Search code examples
androidandroid-constraintlayout

Keep child view inside layout in ConstraintLayout


I have been trying to implement a bar showing progress data with ConstraintLayout. The problem is my TextView goes out of the layout bound when the Guideline is too left or too right of the bar.

I have tried to change the constraints of the TextView from connected to the "@id/guideline" into "parent", and add horizontalBias instead, but then the TextView will become inaccurately positioned (a little shifted to right/left side depends on the yellow bar length)

Here is the image of current result: enter image description here

Here is the layout code:

<android.support.constraint.ConstraintLayout
    android:id="@+id/layoutConstraint"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:paddingLeft="16dp"
    android:paddingTop="100dp"
    android:paddingRight="16dp"
    android:paddingBottom="16dp">

    <android.support.constraint.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent=".01" />

    <TextView
        android:id="@+id/textProgress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="12dp"
        android:textColor="@color/black"
        android:textSize="12sp"
        app:layout_constraintEnd_toEndOf="@id/guideline"
        app:layout_constraintStart_toStartOf="@id/guideline"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="current value" />

    <View
        android:id="@+id/layoutProgress"
        android:layout_width="0dp"
        android:layout_height="8dp"
        android:background="@drawable/background_rounded_corner_light_grey"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textProgress" />

    <View
        android:id="@+id/currentProgress"
        android:layout_width="0dp"
        android:layout_height="8dp"
        android:background="@drawable/background_rounded_corner_yellow"
        app:layout_constraintEnd_toStartOf="@id/guideline"
        app:layout_constraintStart_toStartOf="@id/layoutProgress"
        app:layout_constraintTop_toBottomOf="@id/textProgress" />
</android.support.constraint.ConstraintLayout>

Solution

  • It looks like you want to keep the text "current value" centered on the end of the progress bar; however, if this placement causes the text to be cutoff on either the left side or the right, the text should be aligned to the side of the ConstraintLayout so that it remains entirely visible.

    Your TextView is centered on the percentage guideline that you position programmatically between 0% and 100% (I assume.)

    I suggest that you change the guideline to one based upon pixels (dp or px) and position your TextView that is centered on the new guideline. Instead of varying the guideline position from zero to 100%, calculated a new range:

    Low end of range = 1/2 the width of TextView plus whatever margin.

    High end of range = width of the ConstraintLayout - 1/2 width of the TextView minus whatever margin.

    The margin will remain at the low end of the range while progress falls to the left of the low end. As the progress goes past the low end, the guideline will move to the right by the current location of the progress bar - the low end. The guideline will stop at the high end as progress continues on to 100%.

    If you want to stick with the percentage guideline, compute the low and high ends as outlined above. You can then compute the low and high percentages based upon the width of the container.

    Here is a short demo that uses a simple block for progress:

    enter image description here

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        private ConstraintLayout mLayout;
        private View mProgress;
        private int mProgressPosition;
        private int mGuidelineMin;
        private int mGuidelineMax;
        private Guideline mGuideline;
        private int mIncrement;
        private Handler mHandler = new Handler();
        private int mMargin;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mLayout = findViewById(R.id.layout);
            mGuideline = findViewById(R.id.guideline);
            mProgress = findViewById(R.id.progress);
            mMargin = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 16,
                getResources().getDisplayMetrics()
            );
    
            mLayout.post(new Runnable() {
                private int mLoopCount = 100;
    
                @Override
                public void run() {
                    int textWidth = findViewById(R.id.textView).getWidth();
                    mGuidelineMin = textWidth / 2 + mMargin;
                    mGuidelineMax = mLayout.getWidth() - textWidth / 2 - mMargin;
                    mGuideline.setGuidelineBegin(mGuidelineMin);
                    mIncrement = (mLayout.getWidth() - mProgress.getWidth()) / mLoopCount;
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mProgressPosition += mIncrement;
                            mProgress.setTranslationX(mProgressPosition);
                            int guidelinePosition =
                                Math.max(mGuidelineMin, mProgressPosition + mProgress.getWidth() / 2);
                            if (guidelinePosition > mGuidelineMax) {
                                guidelinePosition = mGuidelineMax;
                            }
                            mGuideline.setGuidelineBegin(guidelinePosition);
                            if (--mLoopCount > 0) {
                                mHandler.postDelayed(this, 100);
                            }
                        }
                    }, 100);
                }
            });
        }
    }
    

    activity_main.xml

    <androidx.constraintlayout.widget.ConstraintLayout 
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <View
            android:id="@+id/progress"
            android:layout_width="15dp"
            android:layout_height="15dp"
            android:background="@android:color/holo_red_light"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.17" />
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Current value"
            android:textSize="20sp"
    
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="@+id/guideline"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.07999998" />
    
        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_begin="140dp" />
    </androidx.constraintlayout.widget.ConstraintLayout>