32

I have this layout of my login activity. I want to overlay progressBar as like it can be done using FrameLayout. How to do this using ConstraintLayout?

<layout>

    <data>

        <variable
            name="vm"
            type="com.app.android.login.vm" />
    </data>

    <ScrollView 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"
        android:fillViewport="true"
        tools:context="com.app.android.login.LoginActivity"
        tools:ignore="missingPrefix">

        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="@dimen/default_view_margin_bottom_8dp">

            <android.support.design.widget.TextInputLayout
                android:id="@+id/til_login_email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:textColorHint="@color/colorSecondaryText"
                app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
                app:layout_constraintBottom_toTopOf="@+id/til_login_password"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_chainStyle="packed">

                <android.support.design.widget.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/login_email"
                    android:imeOptions="actionNext"
                    android:singleLine="true"
                    android:text="@={vm.emailField}"
                    android:textColor="@color/colorPrimaryText" />
            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:id="@+id/til_login_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:textColorHint="@color/colorSecondaryText"
                app:hintTextAppearance="@style/AppTheme.InputLayoutStyle"
                app:layout_constraintBottom_toTopOf="@+id/btn_login_login"
                app:layout_constraintTop_toBottomOf="@+id/til_login_email"
                app:layout_constraintVertical_chainStyle="packed">

                <android.support.design.widget.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/login_password"
                    android:imeOptions="actionDone"
                    android:inputType="textPassword"
                    android:singleLine="true"
                    android:text="@={vm.passwordField}"
                    android:textColor="@color/colorPrimaryText" />
            </android.support.design.widget.TextInputLayout>

            <Button
                android:id="@+id/btn_login_login"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:layout_marginTop="48dp"
                android:onClick="@{vm::login}"
                android:text="@string/login_btn_text"
                android:textColor="@color/colorWhite"
                app:layout_constraintBottom_toTopOf="@+id/textview_login_forgot_password"
                app:layout_constraintTop_toBottomOf="@+id/til_login_password"
                app:layout_constraintVertical_chainStyle="packed" />

            <TextView
                android:id="@+id/textview_login_forgot_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:layout_marginTop="36dp"
                android:gravity="center"
                android:text="@string/login_forgot_password"
                app:layout_constraintBottom_toTopOf="@+id/btn_login_register"
                app:layout_constraintTop_toBottomOf="@+id/btn_login_login"
                app:layout_constraintVertical_chainStyle="packed" />

            <Button
                android:id="@+id/btn_login_register"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/default_view_margin_right_8dp"
                android:layout_marginStart="@dimen/default_view_margin_left_8dp"
                android:text="@string/login_sign_up"
                android:textColor="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent" />

            <ProgressBar
                android:id="@+id/progressBar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="@{vm.progressVisibility}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </android.support.constraint.ConstraintLayout>
    </ScrollView>
</layout>

It looks like this:

enter image description here

I am looking for solution which should work for API level 19+. I don't want to add more hierarchy in my layout by wrapping Button or ProgressBar inside ViewGroup or so.

Ajay S
  • 48,003
  • 27
  • 91
  • 111

14 Answers14

47

There are two options, in each case you add one attribute to your <ProgressBar/> tag. It is either

android:translationZ="2dp"

or

android:elevation="2dp"

API level must be >= 21.

kalabalik
  • 3,792
  • 2
  • 21
  • 50
19

If you want to 100% overlay the target view, constrain all the sides of the overlaying view to the corresponding sides of the target view and set the height and width of the overlaying view to 0dp like the following:

    <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="@id/animation_view"
        app:layout_constraintLeft_toLeftOf="@id/animation_view"
        app:layout_constraintRight_toRightOf="@id/animation_view"
        app:layout_constraintTop_toTopOf="@id/animation_view"/>

Here is a working example. In the following image, a red scrim is placed over an image. The XML follows the image.

enter image description here

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/animation_view"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:src="@mipmap/ic_launcher"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#AAFF0000"
        app:layout_constraintBottom_toBottomOf="@id/animation_view"
        app:layout_constraintLeft_toLeftOf="@id/animation_view"
        app:layout_constraintRight_toRightOf="@id/animation_view"
        app:layout_constraintTop_toTopOf="@id/animation_view" />
</android.support.constraint.ConstraintLayout>

See the documentation for ConstraintLayout for more information.

dsh
  • 12,037
  • 3
  • 33
  • 51
Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • I made changes as you suggested here https://pastebin.com/842ma87r but it is not overlaying `LottieAnimationView` – Ajay S Nov 06 '17 at 16:48
  • Thanks. Can you try with my layout and see why it is not working with my layout ? – Ajay S Nov 06 '17 at 17:29
  • @AjayS That is your code from pastebin in the updated answer except that I used `ImageView` instead of `LottieAnimationView` which I don't have and added a scrim so I could see the overlay. – Cheticamp Nov 06 '17 at 19:14
  • I mean. can you try with code which I have in the question. I am sure you will face same issue – Ajay S Nov 06 '17 at 19:17
  • @AjayS I am unsure of the result you are looking for. I won't have access to a dev environment for several days , so I am unable to look into this matter furrher. – Cheticamp Nov 23 '17 at 10:55
  • Just added an elevation to `View` to make it work `android:elevation="2dp"`. – Shailendra Madda Feb 04 '22 at 10:40
6

Set an elevation on the ProgressBar 2dp seems to work.

android:elevation="2dp"

You could also try setting translationZ as suggested in the answer to a similar question. For me this works on an emulator running API 17 and the progress bar appeared on top as expected. If you get any warning than check your build version

Dipali Shah
  • 3,742
  • 32
  • 47
5

In my observations Android "stacks" the views along the z-axis in the order they appear in the xml file in that the view at the end of the file will be at the top of the z-axis and the view at the start of the file will be at the bottom. Of course once you start setting elevation and zTranslation etc the z-axis stack order will be affected...

so moving the progressBar declaration to the end of the ConstraintLayout should make it appear above the other views, this has worked for me.

ProjectDelta
  • 351
  • 4
  • 12
2

Here is your API 19 solution. It puts a CircularProgressDrawable in an overlay on top of your ConstraintLayout.

This is what it looks like:

enter image description here

What you have to do is:

  1. Get rid of the XML ProgressBar.

  2. Give your XML ConstraintLayout an id, for example:

    android:id="@+id/cl"
    
  3. Add this code to your MainActivity:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        boolean toggle;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            final ConstraintLayout cl = findViewById(R.id.cl);
            cl.setOnClickListener(this);
        }
    
        @Override
        public void onClick(final View view) {
            toggle ^= true;
            if (toggle) {
                startProgress();
            } else {
                stopProgress();
            }
        }
    
        void startProgress() {
            final ConstraintLayout cl = findViewById(R.id.cl);
            final CircularProgressDrawable progressDrawable = new CircularProgressDrawable(this);
            progressDrawable.setColorSchemeColors(Color.MAGENTA);
            progressDrawable.setCenterRadius(50f);
            progressDrawable.setStrokeWidth(12f);
            progressDrawable.setStrokeCap(Paint.Cap.BUTT);
            cl.post(new Runnable() {
                @Override
                public void run() {
                    progressDrawable.setBounds(0, 0, cl.getWidth(), cl.getHeight());
                    cl.getOverlay().add(progressDrawable);
                }
            });
            progressDrawable.start();
        }
    
        void stopProgress() {
            final ConstraintLayout cl = findViewById(R.id.cl);
            final CircularProgressDrawable progressDrawable = new CircularProgressDrawable(this);
            progressDrawable.setColorSchemeColors(Color.MAGENTA);
            progressDrawable.setCenterRadius(50f);
            progressDrawable.setStrokeWidth(12f);
            progressDrawable.setStrokeCap(Paint.Cap.BUTT);
            cl.post(new Runnable() {
                @Override
                public void run() {
                    cl.getOverlay().clear();
                }
            });
            progressDrawable.stop();
        }
    }
    
kalabalik
  • 3,792
  • 2
  • 21
  • 50
1

I want to provide you with an alternative to the XML solution.
You can also add a view programmatically to your root view. (ConstraintLayout)

ViewGroupOverlay is an extra layer that sits on top of a ViewGroup (the "host view") which is drawn after all other content in that view (including the view group's children). Interaction with the overlay layer is done by adding and removing views and drawables.

<android.support.constraint.ConstraintLayout
            android:id="@+id/root_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="@dimen/default_view_margin_bottom_8dp">

If you reference the root in your code you can then add your ProgressBar.
Something like this:

rootLayout.overlay.add(ProgressBar(context).apply {
    measure(View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY))
    layout(0, 0, 100, 100)
})

You can also check out this link for extra info.
And this SO question can also help.

timr
  • 6,668
  • 7
  • 47
  • 79
1

You could try one of these options: 1. Add a relative layout outside your layout as here

 <RelativeLayout
   android:id="@+id/relativelayout_progress"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:visibility="gone"
   android:background="#aa000022" >

   <ProgressBar 
       android:layout_centerInParent="true"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:indeterminateOnly="true" />

</RelativeLayout>
  1. Add a view overlay in your activity's onCreate as described here Android 4.3 User Interface View overlays
fhery021
  • 317
  • 2
  • 7
1

Another easy way to solve this is to change Button to View, change background and size. Overlay it with TextView for replacing old Button text and another View for clickable later

0

You can achieve your goal by setting the Z translation for the view.

Put this method in a helper class (for example: UIUtils) and use it for your view:

/**
 * Set the 'Z' translation for a view
 *
 * @param view         {@link View} to set 'Z' translation for
 * @param translationZ 'Z' translation as float
 */
public static void setTranslationZ(View view, float translationZ) {
    ViewCompat.setTranslationZ(view, translationZ);
}

USAGE:

UIUTils.setTranslationZ(YOUR_VIEW, 5);
blueware
  • 5,205
  • 1
  • 40
  • 60
0

Updated Solution for 2020

Kotlin language

I have 2 scenarios, the first is just a normal button and the second one is progress on the center of the button with preventing the click action

here is just shortcode if you want to know how I handle button and progress bar

Use jsut these two method if you want get an idea

private fun disableButton() {
    button.run {
        preservedButtonText = text.toString()
        text = ""
        isClickable = true
        isEnabled = false
    }
    progressBar.visibility = View.VISIBLE

}

private fun enableButton() {
    button.run {
        text = preservedButtonText
        isClickable = false
    }
    progressBar.visibility = View.INVISIBLE
}

and surprise is had for you a class that does all the stuff you just use it I handle create parent constraint layout and it's child's (button, progress bar) programmatically

 /**
 * Created by Amirahmad Adibi.
 * XProject | Copyrights 2019-12-16.
 */

class ZProgressButton(context: Context, var attributeSet: AttributeSet) :
    ConstraintLayout(context, attributeSet) {
    var isLoading: Boolean = false
        set(value) {
            handelShowing(value)
            field = value
        }

    var preservedButtonText: String = ""
    var button: Button
    var progressBar: ProgressBar

    init {
        resetPadding()

        button = getButton(context, attributeSet)
        progressBar = getProgressBar(context, attributeSet)
        preservedButtonText = button.text.toString()

        addView(button)
        addView(progressBar)
        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        progressBar.elevation = 2f
        ViewCompat.setTranslationZ(progressBar, 5f)
        ViewCompat.setTranslationZ(button, 1f)
        handleConstraint(this, button, progressBar)
    }

    private fun handleConstraint(
        view: ConstraintLayout,
        button: Button,
        progressBar: ProgressBar
    ) {
        with(ConstraintSet()) {
            clone(view)
            connect(button.id, ConstraintSet.RIGHT, view.id, ConstraintSet.RIGHT)
            connect(button.id, ConstraintSet.LEFT, view.id, ConstraintSet.LEFT)
            connect(button.id, ConstraintSet.TOP, view.id, ConstraintSet.TOP)
            connect(button.id, ConstraintSet.BOTTOM, view.id, ConstraintSet.BOTTOM)
            connect(progressBar.id, ConstraintSet.RIGHT, view.id, ConstraintSet.RIGHT)
            connect(progressBar.id, ConstraintSet.LEFT, view.id, ConstraintSet.LEFT)
            connect(progressBenter code herear.id, ConstraintSet.TOP, view.id, ConstraintSet.TOP)
            connect(progressBar.id, ConstraintSet.BOTTOM, view.id, ConstraintSet.BOTTOM)
            applyTo(view)
        }
    }

    private fun getButton(context: Context, attributeSet: AttributeSet): Button {
        return ZButton(context, attributeSet).run {
            id = View.generateViewId()
            text = "text"
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            isClickable = true
            return@run this
        }
    }

    private fun getProgressBar(context: Context, attributeSet: AttributeSet): ProgressBar {
        return ProgressBar(context, attributeSet).run {
            id = View.generateViewId()
            visibility = View.VISIBLE
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
            setPadding(4, 4, 4, 4)
            return@run this
        }
    }


    fun resetPadding() {
        setPadding(0, 0, 0, 0)
    }


    fun handelShowing(value: Boolean) {
        removeView(button)
        removeView(progressBar)
        if (value) {
            disableButton()
        } else {
            enableButton()
        }
        addView(button)
        addView(progressBar)
    }

    private fun disableButton() {
        button.run {
            preservedButtonText = text.toString()
            text = ""
            isClickable = true
            isEnabled = false
        }
        progressBar.visibility = View.VISIBLE

    }

    private fun enableButton() {
        button.run {
            text = preservedButtonText
            isClickable = false
        }
        progressBar.visibility = View.INVISIBLE
    }
}

Usage :

buttonLoading.setOnClickListener {
    progressButton.isLoading = true
}
buttonDone.setOnClickListener {
    progressButton.isLoading = false
}
AmirahmadAdibi
  • 358
  • 3
  • 9
0

android:elevation is not working in API level 19. A simple trick will be used to use a card view and set the elevation of this card

 <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:padding="@dimen/padding_10"
        app:contentPadding="16dp"/>
Ishtdeep Hora
  • 310
  • 2
  • 4
0

With the above case, I fixed it this way

<FrameLayout
        android:layout_marginTop="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ProgressBar
            android:id="@+id/progress_reset_password"
            android:visibility="visible"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminateTint="@color/colorAccent"
            android:layout_gravity="center_horizontal"
            android:translationZ="2dp"
            android:elevation="2dp"/>

        <Button
            android:id="@+id/btnGetOTP"
            style="@style/styleButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="80dp"
            android:layout_marginRight="80dp"
            android:text="@string/check_email"
            android:textColor="@color/colorWhite"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


    </FrameLayout>

Open the image above here

0

I added overlays to the image view layout using an extra image. Also, put the extra overlay image view just immediately below the main image view.

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="@dimen/spacing_small"
        android:layout_marginTop="@dimen/spacing_normal">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/imageViewBlog"
                android:layout_width="match_parent"
                android:layout_height="@dimen/blog_post_height"
                android:scaleType="centerCrop"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_placeholder" />

            <androidx.appcompat.widget.AppCompatImageView
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:scaleType="fitXY"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/black_overlay" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tvBlogTopTitle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/spacing_small"
                android:ellipsize="end"
                android:fontFamily="sans-serif-medium"
                android:maxLines="1"
                android:textColor="@color/pale_grey"
                app:layout_constraintBottom_toTopOf="@+id/tvBlogBigTitle"
                app:layout_constraintEnd_toEndOf="@+id/tvBlogBigTitle"
                app:layout_constraintStart_toStartOf="@id/tvBlogBigTitle"
                tools:text="Travel Inspiration" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tvBlogBigTitle"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="@dimen/spacing_normal"
                android:layout_marginLeft="@dimen/spacing_normal"
                android:layout_marginEnd="@dimen/spacing_normal"
                android:layout_marginRight="@dimen/spacing_normal"
                android:layout_marginBottom="@dimen/spacing_normal"
                android:ellipsize="end"
                android:maxLines="2"
                android:textColor="@color/white"
                android:textSize="@dimen/font_tiny"
                app:layout_constraintBottom_toBottomOf="@+id/imageViewBlog"
                app:layout_constraintEnd_toEndOf="@id/imageViewBlog"
                app:layout_constraintStart_toStartOf="@id/imageViewBlog"
                tools:text="This Photographer is Selling Prints of Her Wildlife Photos to \n in the Masai Mara…" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>

Looks like the below picture.

enter image description here

Overlay image:

enter image description here

Shihab Uddin
  • 6,699
  • 2
  • 59
  • 74
-1

There are lots of examples here on how to set z order in xml, which are great. However, if you if you end up needing to programmatically adjust view overlays, and those views happen to be buttons, make sure to set stateListAnimator to null in your xml layout file. stateListAnimator is android's under-the-hood process to adjust translationZ of buttons when they are clicked, so the button that is clicked ends up visible on top. This is not always what you want... for full Z order control, do this:

enter image description here