I created the image slider using smarteist image slider github library and it's working fine. I wanted to show 3 items at a time but Item occupies whole space and visible only 1 item. I also tried recyclerview to achieve this thing but can't get same feel as image slider. So guide me how to achieve it.
Expected
To show 3 items at a time before slide.
what I had done
val partner_recyclerView: SliderView = findViewById(R.id.partnership_slider)
var partnerAdapter: PartnerAdapter = PartnerAdapter(partnerList)
partner_recyclerView.autoCycleDirection = SliderView.LAYOUT_DIRECTION_LTR
partner_recyclerView.setSliderAdapter(partnerAdapter)
partner_recyclerView.scrollTimeInSec = 3
partner_recyclerView.isAutoCycle = true
partner_recyclerView.startAutoCycle()
Item_layout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_height="80dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginTop="8dp"
app:cardElevation="8dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="#+id/myPartnerLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:contentDescription="#string/app_name"
android:scaleType="fitXY"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.cardview.widget.CardView></androidx.constraintlayout.widget.ConstraintLayout>
Main_Layout
<com.smarteist.autoimageslider.SliderView
android:id="#+id/partnership_slider"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_centerInParent="true"
app:sliderAnimationDuration="600"
app:sliderAutoCycleDirection="back_and_forth"
app:sliderScrollTimeInSec="1"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="#+id/constraintLayout3"
app:layout_constraintEnd_toEndOf="#+id/constraintLayout3"
app:layout_constraintStart_toStartOf="#+id/constraintLayout3"
app:layout_constraintTop_toTopOf="#+id/constraintLayout3"/>
Adapter_class
class PartnerAdapter (val mList: ArrayList<PartnershipData>) :
SliderViewAdapter<PartnerAdapter.SliderViewHolder>() {
override fun getCount(): Int {
return mList.size
}
override fun onCreateViewHolder(parent: ViewGroup?): PartnerAdapter.SliderViewHolder {
val inflate: View = LayoutInflater.from(parent!!.context).inflate(R.layout.partner_item, null)
return PartnerAdapter.SliderViewHolder(inflate)
}
override fun onBindViewHolder(viewHolder: PartnerAdapter.SliderViewHolder?, position: Int) {
if (viewHolder != null) {
// if view holder is not null we are simply
// loading the image inside our image view using glide library
Glide.with(viewHolder.itemView).load(mList.get(position).pwa_iconpng).fitCenter()
.into(viewHolder.myPartnerLogo)
}
}
class SliderViewHolder (itemView: View?) : SliderViewAdapter.ViewHolder(itemView) {
val myPartnerLogo: ImageView = itemView!!.findViewById(R.id.myPartnerLogo)
}}
For better understanding
Expected
Problem
I am displaying a carousel within a RecyclerView. I implemented this carousel using ViewPager2. So the requirement is that the carousel should have no padding/margins and thus extend all the way to edge of the screen. However when displaying the first or last item, there should be a bit of whitespace to the left or right respectively. I've achieved that requirement using ItemDecoration applied to the ViewPager2:
class CustomItemDecoration(
private val leftMargin: Int,
private val rightMargin: Int,
private val firstLastMargin: Int,
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
with(outRect) {
right = rightMargin
left = leftMargin
if (position == 0) {
left += firstLastMargin
} else if (position == state.itemCount - 1) {
right += firstLastMargin
}
}
}
}
However, this has caused the first and last item layouts suddenly having extra whitespace at the bottom. I do not understand why this is the case. Please see the following picture of the first item and the second item, note the whitespace present in the first item that is encircled:
My item layout looks like this:
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have seen this question and while it's similar, I do not understand how the accepted answer fixes the issue? How to resolve this issue?
just use android:scaleType in AppCompatImageView
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
to
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="#drawable/splash_screen"
android:scaleType="fitXY"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
//other wise android:scaleType="center"
I'd like to use CoordinatoryLayout, AppBarLayout and CollapsingToolbarLayout to create a layout that resembles the below example from Google Calendar.
The key things I'm trying to replicate:
Scrolling the content behind the status bar
Rounded corners at the top of the scroll container
Enough room at the top of the screen for the header to not look squashed
The Question
Google Calendar appears to be growing the scroll container as the user scrolls. How would I go about doing this or something similar to achieve the look I'm after?
I've put together a quick example of what I'm trying to build:
activity_scrolling.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:animateLayoutChanges="true"
tools:context="uk.co.exampleapplication.ScrollingActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<include
android:id="#+id/lay_header"
layout="#layout/layout_header" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/scroll_header_background"
android:elevation="16dp"
android:paddingBottom="12dp">
<TextView
android:id="#+id/sectionTitleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:text="Title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/filter_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
android:layout_marginEnd="16dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context=".ScrollingActivity"
tools:showIn="#layout/activity_scrolling">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/text_margin"
android:text="#string/large_text" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
layout_header.xml
<?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/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingTop="60dp"
android:paddingBottom="40dp"
app:layout_collapseMode="parallax"
tools:ignore="HardcodedText">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="Title"
android:textColor="#FFF"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Subtitle"
android:textColor="#FFF"
android:textSize="16sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>
scroll_header_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="20dp"
android:topRightRadius="20dp" />
<solid android:color="#FFFFFF" />
<size
android:width="64dp"
android:height="64dp" />
</shape>
My attempt is included below. The header scrolls behind the toolbar as desired but I'd like some additional top padding above my views (about the height of the top inset/ status bar would be sufficient). Google Calendar appears to be solving this by having the container grow as the user scrolls.
Implement an AppBarLayout.OnOffsetChangedListener that will adjust top padding on the ConstraintLayout that holds the TextView and the Button. (Call this view "viewToGrow".) You can also do other things in the listener like change the corner radius of the drawable as the appbar scrolls.
The following example adjusts top padding to give the header more room. This padding is increased as the appbar scrolls up and decreases as it scrolls down. The demo app also removes the corner radius of the drawable during the final 15% of the appbar scroll.
ScrollingActivity
class ScrollingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scrolling)
val viewToGrow: View = findViewById(R.id.viewToGrow)
val baseTopPadding = viewToGrow.paddingTop
// Determine how much top padding has to grow while the app bar scrolls.
var maxDeltaPadding = 0
val contentView = findViewById<View>(android.R.id.content)
ViewCompat.setOnApplyWindowInsetsListener(contentView) { _, insets ->
maxDeltaPadding = insets.systemWindowInsetTop
insets
}
// Get key metrics for corner radius shrikage.
var backgroundRadii: FloatArray? = null
var maxRadius: FloatArray? = null
val backgroundDrawable = (viewToGrow.background as GradientDrawable?)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && backgroundDrawable != null) {
backgroundRadii = backgroundDrawable.cornerRadii
maxRadius = floatArrayOf(backgroundRadii!![0], backgroundRadii[1])
}
// Set up the app bar and the offset change listener.
val appBar: AppBarLayout = findViewById(R.id.app_bar_layout)
val appBarTotalScrollRange: Float by lazy {
appBar.totalScrollRange.toFloat()
}
appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
// Add/remove padding gradually as the appbar scrolls.
val percentOfScrollRange = (-verticalOffset / appBarTotalScrollRange)
val deltaPadding = maxDeltaPadding * percentOfScrollRange
val newTopPadding = baseTopPadding + deltaPadding.toInt()
if (newTopPadding != viewToGrow.paddingTop) {
viewToGrow.setPadding(
viewToGrow.paddingLeft,
newTopPadding,
viewToGrow.paddingRight,
viewToGrow.paddingBottom
)
// Change the drawable radius as the appbar scrolls.
if (backgroundRadii != null && maxRadius != null) {
val radiusShrinkage = if (percentOfScrollRange > (1.0f - CORNER_SHRINK_RANGE)) {
(1.0f - percentOfScrollRange) / CORNER_SHRINK_RANGE
} else {
1.0f
}
backgroundRadii[0] = maxRadius[0] * radiusShrinkage
backgroundRadii[1] = maxRadius[1] * radiusShrinkage
backgroundRadii[2] = maxRadius[0] * radiusShrinkage
backgroundRadii[3] = maxRadius[1] * radiusShrinkage
backgroundDrawable!!.cornerRadii = backgroundRadii
}
}
})
}
companion object {
const val CORNER_SHRINK_RANGE = 0.15f
}
}
I'm trying to create a nice looking menu to change the map type, exactly like what is on Google Maps.. It appears when you click the layers FAB. I don't know if it's a custom FAB menu, or if it animates and opens a fragment.
How to achieve this look?
After much reading and learning, I have managed to fully design the look that I am happy with. For anyone wanting the same look, here is my code and resources below...
Note: It is not exactly perfect, may look different on some other devices, and don't judge my coding too much (I am a beginner... I can some refactoring but it gets the job done).
Extra Points:
Written in Kotlin
Pretty sure requires API 21+. Have not implemented any workaround here for earlier versions
Open/close of the selection view uses a circular reveal animation, instead of Googles expanding one (I like my implementation better)
Lack of optimizations may lead to poor performance on low end devices
Resources
type_default.png
type_satellite.png
type_terrain.png
rounded_rectangle.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#2962ff" />
<corners android:radius="10dp" />
</shape>
ic_map_layers.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#3C4043"
android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27
-7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
</vector>
Map Layout File
The code listed here was also nested inside a constraint layout with the map and other FAB, but if you are reading this, I don't think you need to know how to put a map in...
<android.support.design.widget.FloatingActionButton
android:id="#+id/map_type_FAB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
android:clickable="true"
android:focusable="true"
app:backgroundTint="#FFF"
app:fabSize="mini"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/map_view"
app:rippleColor="#eff5ff"
app:srcCompat="#drawable/ic_map_layers" />
<android.support.constraint.ConstraintLayout
android:id="#+id/map_type_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/map_type_background"
android:elevation="6dp"
android:padding="8dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="#+id/map_type_FAB"
app:layout_constraintTop_toTopOf="#+id/map_type_FAB">
<View
android:id="#+id/map_type_default_background"
android:layout_width="54dp"
android:layout_height="54dp"
android:background="#drawable/rounded_rectangle"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="#+id/map_type_default"
app:layout_constraintEnd_toEndOf="#+id/map_type_default"
app:layout_constraintStart_toStartOf="#+id/map_type_default"
app:layout_constraintTop_toTopOf="#+id/map_type_default" />
<ImageButton
android:id="#+id/map_type_default"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="#drawable/type_default"
android:scaleType="fitCenter"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView13" />
<View
android:id="#+id/map_type_satellite_background"
android:layout_width="54dp"
android:layout_height="54dp"
android:background="#drawable/rounded_rectangle"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="#+id/map_type_satellite"
app:layout_constraintEnd_toEndOf="#+id/map_type_satellite"
app:layout_constraintStart_toStartOf="#+id/map_type_satellite"
app:layout_constraintTop_toTopOf="#+id/map_type_satellite" />
<ImageButton
android:id="#+id/map_type_satellite"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="32dp"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:background="#drawable/type_satellite"
android:scaleType="fitCenter"
app:layout_constraintEnd_toStartOf="#+id/map_type_terrain"
app:layout_constraintStart_toEndOf="#+id/map_type_default"
app:layout_constraintTop_toBottomOf="#+id/textView13" />
<View
android:id="#+id/map_type_terrain_background"
android:layout_width="54dp"
android:layout_height="54dp"
android:background="#drawable/rounded_rectangle"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="#+id/map_type_terrain"
app:layout_constraintEnd_toEndOf="#+id/map_type_terrain"
app:layout_constraintStart_toStartOf="#+id/map_type_terrain"
app:layout_constraintTop_toTopOf="#+id/map_type_terrain" />
<ImageButton
android:id="#+id/map_type_terrain"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:background="#drawable/type_terrain"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView13" />
<TextView
android:id="#+id/textView13"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:fontFamily="sans-serif"
android:text="Map Type"
android:textAllCaps="true"
android:textColor="#android:color/black"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/map_type_default_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Default"
android:textColor="#808080"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="#+id/map_type_default"
app:layout_constraintStart_toStartOf="#+id/map_type_default"
app:layout_constraintTop_toBottomOf="#+id/map_type_default" />
<TextView
android:id="#+id/map_type_satellite_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Satellite"
android:textColor="#808080"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="#+id/map_type_satellite"
app:layout_constraintStart_toStartOf="#+id/map_type_satellite"
app:layout_constraintTop_toBottomOf="#+id/map_type_satellite" />
<TextView
android:id="#+id/map_type_terrain_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Terrain"
android:textColor="#808080"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="#+id/map_type_terrain"
app:layout_constraintStart_toStartOf="#+id/map_type_terrain"
app:layout_constraintTop_toBottomOf="#+id/map_type_terrain" />
</android.support.constraint.ConstraintLayout>
Map Fragment
Snippet of code from my onMapReady function. This is the part that isn't the prettiest, but gets the job done.
override fun onMapReady(googleMap: GoogleMap) {
// Initialise the map variable
map = googleMap
// When map is initially loaded, determine which map type option to 'select'
when {
map.mapType == GoogleMap.MAP_TYPE_SATELLITE -> {
map_type_satellite_background.visibility = View.VISIBLE
map_type_satellite_text.setTextColor(Color.BLUE)
}
map.mapType == GoogleMap.MAP_TYPE_TERRAIN -> {
map_type_terrain_background.visibility = View.VISIBLE
map_type_terrain_text.setTextColor(Color.BLUE)
}
else -> {
map_type_default_background.visibility = View.VISIBLE
map_type_default_text.setTextColor(Color.BLUE)
}
}
// Set click listener on FAB to open the map type selection view
mapTypeFAB.setOnClickListener {
// Start animator to reveal the selection view, starting from the FAB itself
val anim = ViewAnimationUtils.createCircularReveal(
map_type_selection,
map_type_selection.width - (map_type_FAB.width / 2),
map_type_FAB.height / 2,
map_type_FAB.width / 2f,
map_type_selection.width.toFloat())
anim.duration = 200
anim.interpolator = AccelerateDecelerateInterpolator()
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
super.onAnimationEnd(animation)
map_type_selection.visibility = View.VISIBLE
}
})
anim.start()
mapTypeFAB.visibility = View.INVISIBLE
}
// Set click listener on the map to close the map type selection view
map.setOnMapClickListener {
// Conduct the animation if the FAB is invisible (window open)
if (map_type_FAB.visibility == View.INVISIBLE) {
// Start animator close and finish at the FAB position
val anim = ViewAnimationUtils.createCircularReveal(
map_type_selection,
map_type_selection.width - (map_type_FAB.width / 2),
map_type_FAB.height / 2,
map_type_selection.width.toFloat(),
map_type_FAB.width / 2f)
anim.duration = 200
anim.interpolator = AccelerateDecelerateInterpolator()
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
map_type_selection.visibility = View.INVISIBLE
}
})
// Set a delay to reveal the FAB. Looks better than revealing at end of animation
Handler().postDelayed({
kotlin.run {
mapTypeFAB.visibility = View.VISIBLE
}
}, 100)
anim.start()
}
}
// Handle selection of the Default map type
map_type_default.setOnClickListener {
map_type_default_background.visibility = View.VISIBLE
map_type_satellite_background.visibility = View.INVISIBLE
map_type_terrain_background.visibility = View.INVISIBLE
map_type_default_text.setTextColor(Color.BLUE)
map_type_satellite_text.setTextColor(Color.parseColor("#808080"))
map_type_terrain_text.setTextColor(Color.parseColor("#808080"))
map.mapType = GoogleMap.MAP_TYPE_NORMAL
}
// Handle selection of the Satellite map type
map_type_satellite.setOnClickListener {
map_type_default_background.visibility = View.INVISIBLE
map_type_satellite_background.visibility = View.VISIBLE
map_type_terrain_background.visibility = View.INVISIBLE
map_type_default_text.setTextColor(Color.parseColor("#808080"))
map_type_satellite_text.setTextColor(Color.BLUE)
map_type_terrain_text.setTextColor(Color.parseColor("#808080"))
map.mapType = GoogleMap.MAP_TYPE_SATELLITE
}
// Handle selection of the terrain map type
map_type_terrain.setOnClickListener {
map_type_default_background.visibility = View.INVISIBLE
map_type_satellite_background.visibility = View.INVISIBLE
map_type_terrain_background.visibility = View.VISIBLE
map_type_default_text.setTextColor(Color.parseColor("#808080"))
map_type_satellite_text.setTextColor(Color.parseColor("#808080"))
map_type_terrain_text.setTextColor(Color.BLUE)
map.mapType = GoogleMap.MAP_TYPE_TERRAIN
}
}
End Result
This is my end result. Like I said, I am only a beginner, things could definitely be better, but for now, I'm very happy with the result.
The easiest solution i can think of right now is to:
Add a <CardView> to your layout:
...
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab">
<android.support.v7.widget.CardView
android:id="#+id/cardview"
app:layout_constraintEnd_toEndOf="#+id/fab"
app:layout_constraintTop_toTopOf="#+id/fab">
Fill with Views for the dialog
Set Visibility to View.GONE:
<android.support.v7.widget.CardView
android:id="#+id/cardview"
app:layout_constraintEnd_toEndOf="#+id/fab"
app:layout_constraintTop_toTopOf="#+id/fab"
android:visibility="gone">
In your activity set Visibility to View.Visibleon button click:
fab.setOnClickListener{
cardview.visibility = View.VISIBLE
}
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: