I'm trying to make a help popup. Since the help tips will be different according to which screen you click the help button from, I want to put the text inside a scroll just in case. My layout looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/helpPopupTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#string/help_popup_title"
android:textColor="?attr/colorOnBackground"
android:textSize="24sp"
android:textStyle="bold" />
<ScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:fillViewport="true">
<TextView
android:id="#+id/helpTips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/help_tips"
android:textSize="20sp" />
</ScrollView>
<Button
android:id="#+id/footerButton"
style="#style/RoundedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="20dp"
android:text="#string/footer_button_text" />
It's basically a title, the scroll, and a button. My problem is, when the popup shows up, the scroll is just as tall as the text inside of it.
This is how it looks
This is how I build the dialog:
val dialogView = LayoutInflater.from(this).inflate(R.layout.help_popup, null)
val dialogBuilder = AlertDialog.Builder(this).setView(dialogView)
dialogView.findViewById<TextView>(R.id.helpPopupTitle).setText(R.string.help_popup_title)
dialogView.findViewById<TextView>(R.id.helpTips).setText(R.string.help_tips)
val dialog = dialogBuilder.create()
dialog.setCanceledOnTouchOutside(true)
dialogView.findViewById<Button>(R.id.footerButton).setOnClickListener {
dialog.dismiss()
}
dialog.show()
val displayMetrics = DisplayMetrics()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = display
display?.getRealMetrics(displayMetrics)
} else {
#Suppress("DEPRECATION")
val display = windowManager.defaultDisplay
#Suppress("DEPRECATION")
display.getMetrics(displayMetrics)
}
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val popupWidth = width * 0.9
val popupHeight = height * 0.85
dialog.window!!.setLayout(popupWidth.toInt(), popupHeight.toInt())
It is really messy, but so far it works since what I want is a popup that covers most of the screen but not entirely. I've looked in other threads but most just say "just put android:fillViewport="true" and the scroll will fill the parent" but it doesn't work for me, maybe I messed up something while building the popup. Any help?
EDIT: After trying the answer provided by gioravered the weight is actually working and the scroll fills the parent. The only problem is that now the layout is slightly offcentered.
Popup after the edit
Keep your LinearLayout, but add an ID for the root view (mainLayout):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainLayout"
android:orientation="vertical">
<TextView
android:id="#+id/helpPopupTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="#string/help_popup_title"
android:textColor="?attr/colorOnBackground"
android:textSize="24sp"
android:textStyle="bold" />
<ScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:fillViewport="true">
<TextView
android:id="#+id/helpTips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/help_tips"
android:textSize="20sp" />
</ScrollView>
<Button
android:id="#+id/footerButton"
style="#style/RoundedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="20dp"
android:text="#string/footer_button_text" />
</LinearLayout>
What you did was changing the size of the dialog window.
Instead, let's change the size of the Layout itself.
val dialogView = LayoutInflater.from(this).inflate(R.layout.popup, null)
val dialogBuilder = AlertDialog.Builder(this).setView(dialogView)
val dialog = dialogBuilder.create()
dialog.setCanceledOnTouchOutside(true)
dialogView.findViewById<Button>(R.id.footerButton).setOnClickListener {
dialog.dismiss()
}
dialog.show()
val displayMetrics = DisplayMetrics()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = display
display?.getRealMetrics(displayMetrics)
} else {
#Suppress("DEPRECATION")
val display = windowManager.defaultDisplay
#Suppress("DEPRECATION")
display.getMetrics(displayMetrics)
}
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
val popupWidth = screenWidth * 0.9
val popupHeight = screenHeight * 0.85
val mainLayout = dialogView.findViewById<LinearLayout>(R.id.mainLayout)
val params = (mainLayout.layoutParams as? FrameLayout.LayoutParams)?.apply {
width = popupWidth.toInt()
height = popupHeight.toInt()
gravity = Gravity.CENTER;
}
mainLayout.layoutParams = params
The change is this line:
dialogView.findViewById<LinearLayout>(R.id.mainLayout).layoutParams =
Since the layout is the parent layout it will determine the size of the dialog.
I'm triying to create a GridLayout by programmatically in Kotlin. My code for the XML is this:
<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="match_parent"
android:background="#46FF4433"
tools:context=".MainActivity">
<androidx.gridlayout.widget.GridLayout
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:rowCount="5">
app:columnCount="3"
</androidx.gridlayout.widget.GridLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I want to set 15 buttons into my Grid. My mainActivity is this:
val gridLayout: GridLayout = findViewById(R.id.grid)
for (i in 1..10){
val button = Button(this)
button.text = "Boton: " + i
button.setBackgroundColor(Color.parseColor("#ffffff"));
gridLayout.addView(button)
}
But I need each button be like this:
<Button
android:id="#+id/button1"
app:layout_gravity="fill"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:text="Button 1"
android:background="#ffffff"
/>
I think it has something to do with the layout param but I don't know how to set those atributes.
Take a look at GridLayout.LayoutParams and GridLayout.Spec. You can do something like the following:
val button = Button(this)
val lp = GridLayout.LayoutParams()
lp.setGravity(Gravity.FILL)
// GridLayout.spec is immutable, so one can be applied more than once.
// There are several other versions of GridLayout.spec which may be applicable.
lp.rowSpec = GridLayout.spec(row, 1.0f) // row start, weight
lp.columnSpec = GridLayout.spec(col, 1.0f) // column start, weight
button.layoutParams = lp
I haven't tested this, but it should get your started.
I'm adding new fragments dynamically to an existing fragment and I'm doing some calculation to infer the size of the new fragment.
After getting the new size i change the layout params of that new fragment to match the calculated size and this is where all the child views disappear - if i will not change the size the view will be presented perfectly ok.
XML code for the generated fragment:
<?xml version="1.0" encoding="utf-8"?>
<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:id="#+id/playerCard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#339CEF"
android:clickable="true"
android:focusable="true"
tools:context=".playerCard">
<ImageView
android:id="#+id/playerImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:background="#DA4444"
android:clickable="false"
android:contentDescription="TODO"
android:focusable="true"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="#id/textView4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="#drawable/ic_menu_camera"
tools:srcCompat="#drawable/ic_menu_camera" />
<TextView
android:id="#+id/textView4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:background="#color/teal_200"
android:clickable="false"
android:focusable="true"
android:text="TextView"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/playerImage" />
<ImageView
android:id="#+id/imageView3"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#99A9D8"
android:clickable="false"
android:contentDescription="TODO"
android:focusable="true"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is how i change the size on the card onCreateView (sizes were checked and are ok - not zero):
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
var view = inflater.inflate(R.layout.fragment_player_card, container, false)
view.x = this.centerPoint?.x?.toFloat() ?: 0.0f
view.y = this.centerPoint?.y?.toFloat() ?: 0.0f
view.layoutParams.width = this.cardSize?.width ?: 0
view.layoutParams.height = this.cardSize?.height ?: 0
view.requestLayout()
view.setOnTouchListener { v, event ->
onCardTouch(event)
}
return view
}
Also tried to do the size changing in a post statement but got the same results.
Any help will be appreciated.
Please use this solution here:
#Override
public void onResume() {
super.onResume();
getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) getLayoutParameters());
}
private final ViewGroup.LayoutParams getLayoutParameters(){
ViewGroup.LayoutParams layoutParameters = getDialog().getWindow().getAttributes();
layoutParameters.width = MYCALCULATEDSIZE.Width;
layoutParameters.height = MYCALCULATEDSIZE.Height;
return layoutParameters;
}
It's important to make sure that the layout parameters are updated when the Fragment Resumes, otherwise you could risk an exception, where the view has not been inflated yet.
Writing it here, makes sure that the fragment, along with its child views have been inflated properly.
I am designing a Feed in which I want the posts in the form of a Card which I implemented using design library of Google. I have created my layout but as the posts will be fetched dynamically therefore I wanted to dynamically create my layout in Kotlin. For that I created a function which will take Image,Title,Description as the parameters and will return a Card View Object.
To set the attributes in CardView I tried layoutParams but using it just crashed my app
val cardViewParam = cardView.layoutParams as CoordinatorLayout.LayoutParams
In this code I tried every layout type Linear,Relative,Coordinator and even ViewGroup but my app keeps on crashing every time.
This is my layout:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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="match_parent"
tools:context=".Home"
android:background="#color/DirtyWhite"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:tag="FEED">
//This is the card view which I want to create dynamically
<android.support.v7.widget.CardView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:elevation="50dp"
app:cardCornerRadius="10dp">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp"
android:clipToPadding="false">
<ImageView android:layout_width="match_parent"
android:layout_height="180dp"
android:src="#drawable/sample"
android:scaleType="centerCrop"
android:layout_marginTop="-10dp"
android:layout_marginStart="-10dp"
android:layout_marginEnd="-10dp"
android:layout_marginBottom="-10dp"
android:adjustViewBounds="true"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="#style/Base.TextAppearance.AppCompat.Title"
android:text="This is a part of Material Design."
android:ellipsize="end"
android:maxLines="1"
android:layout_marginTop="16dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="#style/TextAppearance.AppCompat.Body2"
android:text="This is a part of Material Design. This is a part of Material Design. This is a part of Material Design. "
android:ellipsize="end"
android:textColor="#color/realGray"
android:maxLines="2"
android:layout_marginTop="8dp"
/>
<RelativeLayout android:layout_width="match_parent"
android:layout_marginTop="8dp"
android:layout_height="wrap_content">
<RatingBar android:layout_width="wrap_content" android:layout_height="wrap_content"
android:numStars="5"
android:rating="3"
android:isIndicator="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerInParent="true"
style="#style/Base.Widget.AppCompat.RatingBar.Small"/>
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="#drawable/ic_favorite_border_black_24dp"
android:minHeight="30dp"
android:minWidth="30dp"
android:layout_marginRight="15dp"
android:layout_marginEnd="15dp"
/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Order Now"
android:background="#drawable/rounded_card_button_small"
android:textColor="#color/DirtyWhite"
android:minHeight="0dp"
android:minWidth="0dp"
android:padding="5dp"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>
I tried this code to create layout dynamically:
(NOTE: This code was made just to create basic Card excluding the Rating Bar and other Action Buttons)
fun createAdsPost(view: View, photo: ImageView, titleText: String, descText: String): CardView {
val cardView = CardView(view.context)
val cardViewParam = cardView.layoutParams as CoordinatorLayout.LayoutParams
cardView.layoutParams = cardViewParam
cardViewParam.width = LinearLayout.LayoutParams.MATCH_PARENT
cardViewParam.height = LinearLayout.LayoutParams.WRAP_CONTENT
cardViewParam.setMargins(dpToPx(5, view), dpToPx(5, view), dpToPx(5, view), dpToPx(5, view))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cardView.elevation = dpToPx(50, view).toFloat()
}
cardView.apply {
radius = dpToPx(10, view).toFloat()
this.layoutParams = cardViewParam
}
val linear1 = LinearLayout(view.context)
linear1.orientation = LinearLayout.VERTICAL
linear1.setPadding(dpToPx(10, view), dpToPx(10, view), dpToPx(10, view), dpToPx(10, view))
linear1.clipToPadding = false
val linear1Param = linear1.layoutParams as LinearLayout.LayoutParams
linear1Param.width = LinearLayout.LayoutParams.MATCH_PARENT
linear1Param.height = LinearLayout.LayoutParams.WRAP_CONTENT
linear1.layoutParams = linear1Param
photo.scaleType = ImageView.ScaleType.CENTER_CROP
photo.adjustViewBounds = true
val photoParams = photo.layoutParams as LinearLayout.LayoutParams
photoParams.apply {
width = LinearLayout.LayoutParams.MATCH_PARENT
height = dpToPx(180, view)
topMargin = dpToPx(-10, view)
bottomMargin = dpToPx(-10, view)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
photoParams.marginStart = dpToPx(-10, view)
photoParams.marginEnd = dpToPx(-10, view)
}
linear1.addView(photo)
val title = TextView(view.context)
title.apply {
ellipsize = TextUtils.TruncateAt.END
maxLines = 1
text = titleText
}
val titleTextParams = title.layoutParams as LinearLayout.LayoutParams
titleTextParams.apply {
width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.WRAP_CONTENT
topMargin = dpToPx(16, view)
}
linear1.addView(title)
val desc = TextView(view.context)
desc.apply {
ellipsize = TextUtils.TruncateAt.END
maxLines = 2
text = descText
setTextColor(Color.parseColor("#626567"))
}
val descTextParams = desc.layoutParams as LinearLayout.LayoutParams
descTextParams.apply {
width = LinearLayout.LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.WRAP_CONTENT
topMargin = dpToPx(8, view)
}
linear1.addView(desc)
cardView.addView(linear1)
return cardView
}
When I am using these codes my app is crashing. I tried to debug my codes and came to know that the first error is in the layoutParams of CardView.
In short, I want to know how can I set attributes in the Card View
Also, Please Tell is this the only way to create dynamic layout or there is a more efficient way. I am new in Android Developing.
I would like to suggest one thing, if your requirement is to create view dynamically then you just try one thing that is :
1) Make a separate layout file for your dymanic view
2) Inflate that layout using :
val layoutInflater = getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
layoutInflater!!.inflate(R.layout.your_layout_file)
3) Now you will have view, then you just have to access the layour views using something like :
view.findViewById(you view id)
4) now you will be able to create and access your view dynamically easily
Note:
getContent() : will come according to your usage on fragment or in activity, so need to change it accordingly
same as inflate method, use parameter according to your requirement
You are casting the LayoutParams as CoordinatorLayout.LayoutParams whereas you are creating a view dynamically that doesn't have a parent view, and that's I think is the issue.
If you are creating the view dynamically than you should create new LayoutParams as well, according to its parent container.
For e.g., if you want to add CardView in a LinearLayout than, the LayoutParams should be as below:
val cardView = CardView(view.context)
val cardViewParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).also {
it.setMargins(dpToPx(5, view), dpToPx(5, view), dpToPx(5, view), dpToPx(5, view))
}
cardView.apply {
radius = dpToPx(10, view).toFloat()
this.layoutParams = cardViewParam
}
...
...
val linear1 = LinearLayout(view.context)
val linear1Param = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
...
...
cardView.addView(linear1)
For linear1, we have taken FrameLayout.LayoutParams as in our case we are going to add it as a child of CardView, which is an extended FrameLayout.
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:
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.
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.
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.
<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.
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
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.
Here is your API 19 solution. It puts a CircularProgressDrawable in an overlay on top of your ConstraintLayout.
This is what it looks like:
What you have to do is:
Get rid of the XML ProgressBar.
Give your XML ConstraintLayout an id, for example:
android:id="#+id/cl"
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();
}
}
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.
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>
Add a view overlay in your activity's onCreate as described here Android 4.3 User Interface View overlays
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
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);
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
}
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"/>
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
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.
Overlay image:
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: