/*
* Copyright (C) 2017 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.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import androidx.constraintlayout.core.widgets.HelperWidget;
/**
* <b>Added in 1.1</b>
* <p>
* A Barrier references multiple widgets as input,
* and creates a virtual guideline based on the most
* extreme widget on the specified side. For example,
* a left barrier will align to the left of all the referenced views.
* </p>
* <p>
* <h2>Example</h2>
* <p><div align="center" >
* <img width="325px" src="resources/images/barrier-buttons.png">
* </div>
* Let's have two buttons, @id/button1 and @id/button2.
* The constraint_referenced_ids field will reference
* them by simply having them as comma-separated list:
* <pre>
* {@code
* <androidx.constraintlayout.widget.Barrier
* android:id="@+id/barrier"
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* app:barrierDirection="start"
* app:constraint_referenced_ids="button1,button2" />
* }
* </pre>
* <p>
* With the barrier direction set to start, we will have the following result:
* <p><div align="center" >
* <img width="325px" src="resources/images/barrier-start.png">
* </div>
* <p>
* Reversely, with the direction set to end, we will have:
* <p><div align="center" >
* <img width="325px" src="resources/images/barrier-end.png">
* </div>
* <p>
* If the widgets dimensions change,
* the barrier will automatically move according to its direction to get
* the most extreme widget:
* <p><div align="center" >
* <img width="325px" src="resources/images/barrier-adapt.png">
* </div>
*
* <p>
* Other widgets can then be constrained to the barrier itself,
* instead of the individual widget. This allows a layout
* to automatically adapt on widget dimension changes
* (e.g. different languages will end up with different length for similar worlds).
* </p>
* <h2>GONE widgets handling</h2>
* <p>If the barrier references GONE widgets,
* the default behavior is to create a barrier on the resolved position of the GONE widget.
* If you do not want to have the barrier take GONE widgets into account,
* you can change this by setting the attribute <i>barrierAllowsGoneWidgets</i>
* to false (default being true).</p>
* </p>
* </p>
*
*/
public class Barrier extends ConstraintHelper {
/**
* Left direction constant
*/
public static final int LEFT = androidx.constraintlayout.core.widgets.Barrier.LEFT;
/**
* Top direction constant
*/
public static final int TOP = androidx.constraintlayout.core.widgets.Barrier.TOP;
/**
* Right direction constant
*/
public static final int RIGHT = androidx.constraintlayout.core.widgets.Barrier.RIGHT;
/**
* Bottom direction constant
*/
public static final int BOTTOM = androidx.constraintlayout.core.widgets.Barrier.BOTTOM;
/**
* Start direction constant
*/
public static final int START = BOTTOM + 2;
/**
* End Barrier constant
*/
public static final int END = START + 1;
private int mIndicatedType;
private int mResolvedType;
private androidx.constraintlayout.core.widgets.Barrier mBarrier;
public Barrier(Context context) {
super(context);
super.setVisibility(View.GONE);
}
public Barrier(Context context, AttributeSet attrs) {
super(context, attrs);
super.setVisibility(View.GONE);
}
public Barrier(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
super.setVisibility(View.GONE);
}
/**
* Get the barrier type ({@code Barrier.LEFT}, {@code Barrier.TOP},
* {@code Barrier.RIGHT}, {@code Barrier.BOTTOM}, {@code Barrier.END},
* {@code Barrier.START})
*/
public int getType() {
return mIndicatedType;
}
/**
* Set the barrier type ({@code Barrier.LEFT}, {@code Barrier.TOP},
* {@code Barrier.RIGHT}, {@code Barrier.BOTTOM}, {@code Barrier.END},
* {@code Barrier.START})
*/
public void setType(int type) {
mIndicatedType = type;
}
private void updateType(ConstraintWidget widget, int type, boolean isRtl) {
mResolvedType = type;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// Pre JB MR1, left/right should take precedence, unless they are
// not defined and somehow a corresponding start/end constraint exists
if (mIndicatedType == START) {
mResolvedType = LEFT;
} else if (mIndicatedType == END) {
mResolvedType = RIGHT;
}
} else {
// Post JB MR1, if start/end are defined, they take precedence over left/right
if (isRtl) {
if (mIndicatedType == START) {
mResolvedType = RIGHT;
} else if (mIndicatedType == END) {
mResolvedType = LEFT;
}
} else {
if (mIndicatedType == START) {
mResolvedType = LEFT;
} else if (mIndicatedType == END) {
mResolvedType = RIGHT;
}
}
}
if (widget instanceof androidx.constraintlayout.core.widgets.Barrier) {
androidx.constraintlayout.core.widgets.Barrier barrier =
(androidx.constraintlayout.core.widgets.Barrier) widget;
barrier.setBarrierType(mResolvedType);
}
}
@Override
public void resolveRtl(ConstraintWidget widget, boolean isRtl) {
updateType(widget, mIndicatedType, isRtl);
}
/**
* @param attrs
*
*/
@Override
protected void init(AttributeSet attrs) {
super.init(attrs);
mBarrier = new androidx.constraintlayout.core.widgets.Barrier();
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ConstraintLayout_Layout);
final int count = a.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.ConstraintLayout_Layout_barrierDirection) {
setType(a.getInt(attr, LEFT));
} else if (attr == R.styleable.ConstraintLayout_Layout_barrierAllowsGoneWidgets) {
mBarrier.setAllowsGoneWidget(a.getBoolean(attr, true));
} else if (attr == R.styleable.ConstraintLayout_Layout_barrierMargin) {
int margin = a.getDimensionPixelSize(attr, 0);
mBarrier.setMargin(margin);
}
}
a.recycle();
}
mHelperWidget = mBarrier;
validateParams();
}
/**
* allows gone widgets to be included in the barrier
* @param supportGone
*/
public void setAllowsGoneWidget(boolean supportGone) {
mBarrier.setAllowsGoneWidget(supportGone);
}
/**
* Find if this barrier supports gone widgets.
*
* @return true if this barrier supports gone widgets, otherwise false
*
* @deprecated This method should be called
* {@code getAllowsGoneWidget} such that {@code allowsGoneWidget}
* can be accessed as a property from Kotlin;
* {@see https://android.github.io/kotlin-guides/interop.html#property-prefixes}.
* Use {@link #getAllowsGoneWidget()} instead.
*/
@Deprecated
public boolean allowsGoneWidget() {
return mBarrier.getAllowsGoneWidget();
}
/**
* Find if this barrier supports gone widgets.
*
* @return true if this barrier supports gone widgets, otherwise false
*/
public boolean getAllowsGoneWidget() {
return mBarrier.getAllowsGoneWidget();
}
/**
* Set a margin on the barrier
*
* @param margin in dp
*/
public void setDpMargin(int margin) {
float density = getResources().getDisplayMetrics().density;
int px = (int) (0.5f + margin * density);
mBarrier.setMargin(px);
}
/**
* Returns the barrier margin
*
* @return the barrier margin (in pixels)
*/
public int getMargin() {
return mBarrier.getMargin();
}
/**
* Set the barrier margin
*
* @param margin in pixels
*/
public void setMargin(int margin) {
mBarrier.setMargin(margin);
}
@Override
public void loadParameters(ConstraintSet.Constraint constraint,
HelperWidget child,
ConstraintLayout.LayoutParams layoutParams,
SparseArray<ConstraintWidget> mapIdToWidget) {
super.loadParameters(constraint, child, layoutParams, mapIdToWidget);
if (child instanceof androidx.constraintlayout.core.widgets.Barrier) {
androidx.constraintlayout.core.widgets.Barrier barrier =
(androidx.constraintlayout.core.widgets.Barrier) child;
ConstraintWidgetContainer container = (ConstraintWidgetContainer) child.getParent();
boolean isRtl = container.isRtl();
updateType(barrier, constraint.layout.mBarrierDirection, isRtl);
barrier.setAllowsGoneWidget(constraint.layout.mBarrierAllowsGoneWidgets);
barrier.setMargin(constraint.layout.mBarrierMargin);
}
}
}