I have the following layout:
<android.support.constraint.ConstraintLayout
android:id="#+id/target"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="#FF0000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Hello"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/left_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LEFT VIEW"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/right_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RIGHT VIEW"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
<Button
android:id="#+id/left_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="LEFT"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/right_btn"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/target" />
<Button
android:id="#+id/right_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="RIGHT"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/left_btn"
app:layout_constraintTop_toBottomOf="#+id/target" />
Which result in this:
This is the fragment:
public class IncreaseWidthLeftRightFragment extends Fragment {
private IncreaseWidthLeftRightViewModel mViewModel;
private TextView mRightView;
private Button mLeftBtn;
private Button mRightBtn;
private TextView mLeftView;
private ConstraintLayout mTarget;
public static IncreaseWidthLeftRightFragment newInstance() {
return new IncreaseWidthLeftRightFragment();
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.increase_width_left_right_fragment, container, false);
mRightView = v.findViewById(R.id.right_view);
mLeftBtn = v.findViewById(R.id.left_btn);
mLeftBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showLeftText();
}
});
mRightBtn = v.findViewById(R.id.right_btn);
mRightBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showRightText();
}
});
mLeftView = v.findViewById(R.id.left_view);
mTarget = v.findViewById(R.id.target);
return v;
}
private void showLeftText() {
// increase the left side of the target container
// ...
mLeftView.setVisibility(View.VISIBLE);
}
private void showRightText() {
// increase the right side of the target container
// ...
mRightView.setVisibility(View.VISIBLE);
}
}
The left_view and right_view TextViews are initially set to visibility GONE. The left button must show the left_view while expanding the left side but the right side should be kept in the same place. Similar for the right side but in the opposite direction.
How could I achieve this? I tried to play with the LayoutParams but without success. I would like to do this with an animation, but that will be the next step.
UPDATE:
Just to be clear, for instance, if I click on the left button, this should be the end result:
As you can see, the right side of the red rectangle is in the same X coordinate, however, the width of the rectangle increase to the left.
If you need your target view be anchored with the text views, they should not be inside of it (supposing you want to use the ConstraintLayout). The text views themselves also should have some anchor on the layout, so they can expand related to it position.
1) Add guidelines
For this purpose you can use constraint guidelines. E.g. if you want the text views (and consequently target view) expand from 32% from left and right edges of the root screen, you can add guidelines as follow (they should be at the same level of hierarchy with your buttons/target view):
<android.support.constraint.Guideline
android:id="#+id/left_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.32" />
<android.support.constraint.Guideline
android:id="#+id/right_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.68" />
P.S. the layout_constraintGuide_percent always calculates from left side of a view the guide is inside of
2) Align your text views
As I said above, the text views should be at the same level with the target view, so grab them from inside the target view and put somewhere in the root constraint layout such that the right view is to right of the right guideline and left view is to left of the left one:
<TextView
android:id="#+id/left_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="LEFT VIEW"
app:layout_constraintEnd_toStartOf="#+id/left_guideline" />
<TextView
android:id="#+id/right_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RIGHT VIEW"
app:layout_constraintStart_toStartOf="#+id/right_guideline" />
We also need set vertical constraint for the views, and make a feeling that they are inside of the target view. In order to do that we need to align top side of both text views with the top side of the target view. In addition to that, the text views initial state should be "folded" (so they are hidden), unfortunately i don't know how to make them of width 0dp, since such value for layout_width or layout_height makes a constraint layout think that the view just comply constraints instead of using it's own size. As a quick workaround let's set width to 1px. To prevent the text views from extending vertically, i also would like to propose set singleLine property for them to true.
<TextView
android:id="#+id/left_view"
android:singleLine="true"
android:layout_width="1px"
android:layout_height="wrap_content"
android:text="LEFT VIEW"
app:layout_constraintEnd_toStartOf="#+id/left_guideline"
app:layout_constraintTop_toTopOf="#+id/target" />
<TextView
android:id="#+id/right_view"
android:singleLine="true"
android:layout_width="1px"
android:layout_height="wrap_content"
android:text="RIGHT VIEW"
app:layout_constraintStart_toStartOf="#+id/right_guideline"
app:layout_constraintTop_toTopOf="#+id/target" />
3) Align the target view
Now just align your target view left and right sides with left and right side of left and right text view respectively (so it's aligned with the outer boundaries of both text views) and set the layout_width attribute to 0dp, that will make it follow constraints instead of plain values.
<android.support.constraint.ConstraintLayout
android:id="#+id/target"
android:layout_width="0dp"
android:layout_height="200dp"
android:background="#FF0000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/right_view"
app:layout_constraintStart_toStartOf="#+id/left_view"
app:layout_constraintTop_toTopOf="parent">
4) Add root layout id
In order to get root layout for animation, add id for the root layout:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent">
After all steps your layout blueprint should look like this:
If you struggle at any step, feel free to use full layout gist from here.
5) Animate expanding
Eventually your show method should look something like that:
...
private void showLeftText() {
expandView(mLeftView);
}
private void showRightText() {
expandView(mRightView);
}
private void expandView(View view) {
TransitionManager.beginDelayedTransition(mRootView);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
view.setLayoutParams(layoutParams);
}
And here is a short demo:
Related
I have an imageView that I want to move given a specific situation.
Initially I have a Relative layout with two textViews and an imageview. The textViews are oriented with one above the other. The imageView is set
android:layout_below="#id/text_view1"
android:layout_toEndOf="#+id/text_view2".
In the logic text_view2 is removed when a specific condition is met. I want to programmatically move the imageView to the end of text_view1 when this condition is met. Essentially when text_view2 is removed, I want to set the imageView to
android:layout_toEndOf="#+id/text_view1"
I don't believe setting X,Y,Z values is appropriate here because programmatically, I don't know where the imageView will show up given different screen sizes, and densities. I just need it to move to the end of the first textView.
Take a look at RelativeLayout.LayoutParams. You will need to manipulate the layout rules in the layout params as follows:
// Make textView2 invisible
tv2.visibility = View.INVISIBLE
// Get the LayoutParams of the ImageView
val ivParams = iv.layoutParams as RelativeLayout.LayoutParams
// Change the rule to be to the right of textView1
ivParams.addRule(RelativeLayout.RIGHT_OF, tv1.id)
// Since the placement of textView2 is changing, request a layout.
iv.requestLayout()
Consider using "END_OF" instead of "RIGHT_OF".
You can either place the Views in a nested LinearLayout or use a ConstraintLayout with a Barrier.
It is generally recommended to use ConstraintLayout because nested LinearLayouts are bad for performance but since ConstraintLayout takes some getting used to, I did not want to omit the other option.
To demonstrate the two approaches I've set up a small example with a LinearLayout and a ConstraintLayout in the same screen:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.TabTwoFragment">
<LinearLayout
android:layout_width="240dp"
android:layout_height="128dp"
android:background="#cccccc">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#44ff0000"
android:maxWidth="160dp"
android:text="Upper TextView\nin\n nested\n LinearLayout" />
<TextView
android:id="#+id/longTextViewInLinearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#4400ff00"
android:maxWidth="160dp"
android:text="Lower TextView in nested LinearLayout" />
</LinearLayout>
<ImageView
android:id="#+id/linearLayoutImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="#ffffff"
android:src="#drawable/ic_android_black_24dp"
android:tint="#color/colorPrimary" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="240dp"
android:layout_height="128dp"
android:layout_gravity="bottom|end"
android:background="#666666">
<TextView
android:id="#+id/shortTextViewInConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#44ff0000"
android:maxWidth="160dp"
android:text="Upper TextView\nin\n nested\nConstraintLayout"
android:textColor="#ffffff"
app:layout_constraintBottom_toTopOf="#+id/longTextViewInConstraintLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="#+id/longTextViewInConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#4400ff00"
android:maxWidth="160dp"
android:text="Lower TextView in ConstraintLayout"
android:textColor="#ffffff"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/shortTextViewInConstraintLayout" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="shortTextViewInConstraintLayout, longTextViewInConstraintLayout" />
<ImageView
android:id="#+id/constraintLayoutImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="#ffffff"
android:importantForAccessibility="no"
android:src="#drawable/ic_android_black_24dp"
android:tint="#color/colorAccent"
app:layout_constraintStart_toEndOf="#+id/barrier"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
If the ImageView is clicked, the longer TextView will disappear and the ImageView will move closer to the short TextView. The animations are provided by Android's transition framework, so basically all you have to do is trigger the transition by calling TransitionManager.beginDelayedTransition()
For demonstration purposes, I've placed all the code in one method. Please note that normally one would have the TransitionSet as field of the Fragment so that it does not have to be recreated every time you need it. (The code is in Java since Android Studio supports automatic translation to Kotlin if required but not the other way round ;-) )
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TransitionSet ts = new TransitionSet();
ts.addTransition( new ChangeBounds());
ts.addTransition(new Slide());
View imageViewInLinearLayout = view.findViewById(R.id.linearLayoutImageView);
imageViewInLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition((ViewGroup)getView(), ts);
view.findViewById(R.id.longTextViewInLinearLayout).setVisibility(GONE);
}
});
View imageViewInConstraintLayout = view.findViewById(R.id.constraintLayoutImageView);
imageViewInConstraintLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition((ViewGroup)getView(), ts);
view.findViewById(R.id.longTextViewInConstraintLayout).setVisibility(GONE);
}
});
}
I have two Buttons organized in a row in a ContraintLayout. By default, I need them to be evenly distributed each taking 50% of the ConstraintLayout width (easy enough with a vertical Guideline set to 50%):
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
<Button
android:id="#+id/secondaryButton"
style="#style/Button.Secondary"
android:text="Secondary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/primaryButtonBarrier"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
<Button
android:id="#+id/primaryButton"
style="#style/Button.Primary"
android:layout_width="0dp"
android:text="Primary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/middleGuideline"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintTop_toBottomOf="#id/detail" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/middleGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/primaryButtonBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="primaryButton, middleGuideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
</androidx.constraintlayout.widget.ConstraintLayout>
Desired behavior (currently achieved programmatically with code snippet below):
I also need to support "overflow" text in the primary Button on the right.
So, if I have a larger amount of text, the Button width will push past the 50% guideline. I've tried using app:layout_constraintWidth_min, but it doesn't take a percentage (wrap or dimen).
The best solution I can come up with is setting the Button width to wrap_content and then the min width programmatically after the ConstraintLayout has been measured:
doOnLayout { constraintLayout ->
val layoutParams = primaryButton.layoutParams as ConstraintLayout.LayoutParams
layoutParams.matchConstraintMinWidth = constraintLayout.measuredWidth / 2
primaryButton.layoutParams = layoutParams
}
I feel like there has to be a combo of settings here that I'm missing to achieve this. Thanks in advance!
There is no straightforward way to do what you want that I know of with any standard XML code and the standard ConstraintLayout attributes. You might be able to do something with a nested layout, but, in general, that should be avoided with ConstraintLayout. I think that the way you devised is probably going to be the way to go.
However, if you want to do this all in XML, you could create a shadow button that is a replica of the primary button in all aspects (position, style, text, etc.) except that it is invisible. A barrier can then ride to the left of the shadow button and the center guideline. The visible primary button can now be constrained to the barrier since the barrier is not dependent upon the primary button. The primary button will at least be 1/2 the width of the ConstraintLayout but can expand beyond.
A second approach would be to create a custom view for the primary button. This custom view can implement an attribute that specifies a minimum width for the button based on the width of the ConstraintLayout.
ButtonMinWidthOfParent.kt
class ButtonMinWidthOfParent #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MaterialButton(context, attrs) {
private var minWidthOfParent = 0f
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.ButtonMinWidthOfParent,
0, 0
).apply {
try {
minWidthOfParent = getFloat(R.styleable.ButtonMinWidthOfParent_minWidthOfParent, 0f)
} finally {
recycle()
}
}
}
#Override
override fun getSuggestedMinimumWidth(): Int {
return maxOf(
super.getSuggestedMinimumWidth(),
((parent as ViewGroup).measuredWidth * minWidthOfParent).toInt()
)
}
}
attrs.xml
<resources>
<declare-styleable name="ButtonMinWidthOfParent">
<attr name="minWidthOfParent" format="float"/>
</declare-styleable>
</resources>
activity_main
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="#+id/detail"
android:layout_width="0dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/secondaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Secondary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/primaryButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
<com.example.secondarybutton.ButtonMinWidthOfParent
android:id="#+id/primaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Primary Button that is a little bit longer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail"
app:minWidthOfParent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/middleGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<View
android:id="#+id/view"
android:layout_width="2dp"
android:layout_height="0dp"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
There are undoubtedly other ways, but the XML way has the advantage of being a pure XML implementation with a little trickery while the custom view is a more standard way to extend view capabilities.
I have a toolbar like component implementation, that I'm having trouble with the layout in all situations. It has a left icon, a title and a right menu/button. I need the title to be centered on the full screen (or at least the full width of the layout) but also to not overlap with the other components. So the width of the title would have to be constrained by the left icon and the right button.
I have two intermediary solutions but can't seem to find a way to combine them.
One is to center the title on the screen. The problem here is that the title overlaps with the right button (and would overlap with the left icon too, if large enough ...). Here is the layout XML:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/bottomSheetHeaderIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/bottomSheetHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAlignment="center"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="My also very very very very very long title" />
<TextView
android:id="#+id/right_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Really long string longer"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The other is to center the title between the left icon and the right button. Now there is no overlap, but the title is not centered correctly. Since the two side elements have very different sizes, the title is only centered between them, which is no good. Here is the same layout with the constraints for this case:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/bottomSheetHeaderIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/bottomSheetHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAlignment="center"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/right_action"
app:layout_constraintStart_toEndOf="#+id/bottomSheetHeaderIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="My also very very very very very long title" />
<TextView
android:id="#+id/right_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Really long string longer"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I'm trying to get away with just a XML layout solution, without having to do programmatically detect overlaps and change the layout.
Solutions with other layouts would also work, if they exist.
The problem is the same as the questions here and here, but I'm hoping that by giving more detail, this one gets an accepted solution.
Here is a programatic answer to this question, that is what I'm using for now, but I'd really like a layout solution ...
I use the first layout, centered on the screen, and, as this is part of a custom view for me, add the following code to the constructor (title is a reference to bottomSheetHeaderTitle and rightAction is right_action, the left icon is assumed to always be smaller than the right action text).
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (title.width > 0) {
if (title.right > rightAction.x) {
title.maxWidth = (2.0 * (width / 2.0 - (width - rightAction.x))).toInt()
}
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
This code detects if the title overlaps the right action, and, if it does, adds a max width to it. The max width is the double of the distance between the center of the screen and the beginning of the right action.
I am trying to build responsive layout, but I am unable to do so. I need to implement view logic presented on those drawings:
(from the bottom) There is a View with fixed size, anchored to the bottom of the screen. Above it there is a TextView. Its minimum height should be space between center guideline and bottom view. When there is a lot of text it should grow above the center guideline. On the top there is an ImageView with maximum height as space between screen top and center guideline and no minimum height. My current solution looks something like this:
<ConstraintLayout>
<ImageView
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="barrier" />
<Textview
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="bottomView" />
<View
android:layout_height="{some}dp"
app:layout_constraintBottom_toBottomOf="parent" />
<android.support.constraint.Barrier
app:barrierDirection="top"
app:constraint_referenced_ids="guidelineCenter,textView" />
<ConstraintLayout>
At this point a case with long text works fine, but if text is short I end up with text anchored to the bottom view. Any idea how force this minimum constraints required for left image?
As far as I know, there isn't a way through XML to defined a percentage minimum height, so you will have to enforce a minimum height in code. Here are two images that show a short amount of text and one with a longer amount. The red line is there to show where the 1/2 way point is and is not needed. The guideline set at 50% is also not needed.
activity_main.xml
<android.support.constraint.ConstraintLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#mipmap/ic_launcher" />
<TextView
android:id="#+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="This is big text "
app:layout_constraintBottom_toTopOf="#id/bottomView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="#+id/bottomView"
android:layout_width="0dp"
android:layout_height="50dp"
android:background="#color/colorPrimary"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<android.support.constraint.Guideline
android:id="#+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<View
android:id="#+id/view"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#android:color/holo_red_light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#id/guideline" />
</android.support.constraint.ConstraintLayout>
MainActivity.xml
public class MainActivity extends AppCompatActivity {
private ConstraintLayout mLayout;
private TextView mTextView;
private View mBottomView;
private int mMinHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout = findViewById(R.id.layout);
mTextView = findViewById(R.id.textView);
mBottomView = findViewById(R.id.bottomView);
mTextView.setText(getResources().getString(R.string.large_text).substring(0, 1000));
mLayout.post(new Runnable() {
#Override
public void run() {
mMinHeight = mLayout.getHeight() / 2 - mBottomView.getHeight();
if (mTextView.getHeight() < mMinHeight) {
mTextView.setMinHeight(mMinHeight);
}
}
});
}
}
I got two TextViews inside a ConstraintLayout. One on top left, the other to the right of the first one. Nothing crazy. Now, the first view can grow wider which pushes the second view more to the right. What I want though is that the second view always stays within the bounds of the parent.
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:background="#FFFFFF"
android:paddingEnd="20dp">
<TextView
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text 1"
android:ellipsize="end"
android:maxLines="1"
android:background="#FF0000"/>
<TextView
android:id="#+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="#id/text1"
android:text="Text 2"
android:maxLines="1"
android:background="#00FF00"/>
...
What I get is something like this
But when I increase the text length, the second view is being pushed out of the view
What should happen is that the second view is on top right of the parent and the first one takes the rest of the available space. Is there any way I can do this in a ConstraintLayout?
You can use android:maxWidth in the activity_main.xml with a fixed number, but it is not good because every phone has diffirent screen size, so I recommend you set it with code, you can setMaxWidth for text1 following width of text2 and constraintLayout:
txt1 = (TextView) findViewById(R.id.text1);
txt2 = (TextView) findViewById(R.id.text2);
constraintLayout = (ConstraintLayout) findViewById(R.id.constraintLayout);
ViewTreeObserver vto = constraintLayout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
constraintLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
constraintLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
int text2Width = txt2.getWidth();
int layoutWidth = constraintLayout.getWidth();
txt1.setMaxWidth(layoutWidth-text2Width);
}
});
The only way I can see to do this in constraintLayout is to make the 2 textView's a chain and apply layout_constrainedWidth to the first text view so the second text view gets priority:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="#fff"
tools:layout_width="200dp">
<TextView
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FF0000"
android:ellipsize="end"
android:maxLines="1"
android:text="Text 1"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="#id/text2"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00FF00"
android:maxLines="1"
android:text="Text 2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/text1" />
</android.support.constraint.ConstraintLayout>
Short Text 1:
Long Text 1: