I have to implement a banner according to the following designs:
The complexity here is in the shadow of the round logo, the shadow of the logo circle is the continuation of the shadow of the rectangular card of the banner. The border of the shadow is outlined in the following image:
Of course the shadow shouldn't be casted on the surface below the top side of the card. Also the logo center has some offset from the border of the card.
How can I achieve this effect? Standard android shapes doesn't allow to form such a contour. Drawing everything manually seems to be too complex decision for the problem. We have minSdkVersion 21.
You can achieve it using this trick
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="20dp"
app:cardCornerRadius="56dp"
app:cardElevation="16dp"
android:layout_width="56dp"
android:layout_height="56dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="match_parent"
android:src="#color/colorPrimary"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
app:cardElevation="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="48dp"
android:layout_width="350dp"
android:layout_height="500dp">
<LinearLayout
android:layout_marginTop="-28dp"
android:layout_gravity="center_horizontal"
android:layout_width="56dp"
android:layout_height="56dp">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="match_parent"
android:src="#color/colorPrimary"
android:layout_height="match_parent"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
The result will be:
The main idea create two cardview with images, one under the main card and another one in the cardview and using margins make the look like one circle.
I've ended up on using the MaterialShapeDrawable class. It allows to customize drawable edges by manually defining how the edge should de drawn. This is achieved by deriving EdgeTreatment class (similar is possible for corners with CornerTreatment).
As a result the background of the banner looks like this:
Here is the banner top edge treatment class:
private class BannerTopEdgeTreatment(private val circleRadius: Int,
private val circleCenterOffset: Int) : EdgeTreatment(), Cloneable {
init {
// do not allow to offset circle center up
if (circleCenterOffset < 0)
throw IllegalArgumentException()
}
override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
// use interpolated radius
val radius = circleRadius * interpolation
// if circle lays entirely inside the rectangle then just draw a line
val circleTop = circleCenterOffset - radius
if (circleTop >= 0) {
shapePath.lineTo(length, 0f)
return
}
// calc the distance from the center of the edge to the point where arc begins
// ignore the case when the radius is so big that the circle fully covers the edge
// just draw a line for now, but maybe it can be replaced by drawing the arc
val c = sqrt(radius.pow(2) - circleCenterOffset.toDouble().pow(2))
if (c > center) {
shapePath.lineTo(length, 0f)
return
}
// draw a line from the left corner to the start of the arc
val arcStart = center - c
shapePath.lineTo(arcStart.toFloat(), 0f)
// calc the start angle and the sweep angle of the arc and draw the arc
// angles are measured clockwise with 0 degrees at 3 o'clock
val alpha = Math.toDegrees(asin(circleCenterOffset / radius).toDouble())
val startAngle = 180 + alpha
val sweepAngle = 180 - 2 * alpha
shapePath.addArc(
center - radius,
circleCenterOffset - radius,
center + radius,
circleCenterOffset + radius,
startAngle.toFloat(),
sweepAngle.toFloat())
// draw the line from the end of the arc to the right corner
shapePath.lineTo(length, 0f)
}
}
The method to construct a background drawable for banner:
fun createBannerBackgroundDrawable(backgroundColor: ColorStateList,
#Px circleRadius: Int,
#Px circleCenterOffset: Int,
#Px cornersRadius: Int,
#Px elevation: Int): Drawable {
val appearanceModel = ShapeAppearanceModel.builder()
.setTopEdge(BannerTopEdgeTreatment(circleRadius, circleCenterOffset))
.setAllCorners(CornerFamily.ROUNDED, cornersRadius.toFloat())
.build()
val drawable = MaterialShapeDrawable(appearanceModel)
drawable.fillColor = backgroundColor
drawable.elevation = elevation.toFloat()
return drawable
}
Then this drawable is used as a background of the banner view:
banner.background = createVerticalBannerBackground(...)
And also it is necessary to set clipChildren attribute of the parent view of the banner to false:
android:clipChildren="false"
The final result:
Related
How to make the bottom navigation view to a specific shape?
I'd like to have a bottom navigation view of this shape:
I have tried setting it as background of my bottom nav view as:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/navigationBottomView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#drawable/bg_nav_bar"
app:itemHorizontalTranslationEnabled="true"
app:itemIconTint="#drawable/bottom_bar_selector"
app:itemTextColor="#drawable/bottom_bar_selector"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/nav_menu"/>
But it doesn't seem to work.
Any help will be appreciated. Thanks!
The BottomNavigationView by default has a background of MaterialShapeDrawable so you can change its shape using the ShapeAppearanceModel by defining a custom TopEdge EdgeTreatment to draw the half-circle above the BottomNavigationView. To be able to draw something above the BottomNavigationView you need to have a parent which has the below attributes:
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="35dp"
An Xml sample will be like the below:
<?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"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black">
<RelativeLayout
android:id="#+id/bottomNavigationViewParentRL"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="35dp"
android:background="#android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:backgroundTint="#color/white"
app:elevation="2dp"
app:labelVisibilityMode="labeled"
app:itemIconSize="25dp"
app:itemIconTint="#color/item_icon_tint_selector"
app:itemTextColor="#color/item_text_color_selector"
app:menu="#menu/bottom_nav_menu" />
</RelativeLayout>
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/bottomNavigationViewParentRL"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then draw the shape like the below:
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val materialShapeDrawable = bottomNavigationView.getBackground() as MaterialShapeDrawable
materialShapeDrawable.shapeAppearanceModel = materialShapeDrawable.shapeAppearanceModel
.toBuilder()
.setTopEdge(CutoutCircleEdgeTreatment(resources, 70.toFloat(), 10.toFloat()))
.build()
where CutoutCircleEdgeTreatment is a subclass of EdgeTreatment to draw the half-circle at the top which is similar code like the build-in BottomAppBarTopEdgeTreatment class which draws a semi-circular cutout from the top edge to bottom:
class CutoutCircleEdgeTreatment(res: Resources, circleDiameterDp: Float, circleLeftRightOffsetDp: Float) : EdgeTreatment() {
private val fabDiameter: Float
private val offset: Float
init {
fabDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleDiameterDp, res.getDisplayMetrics())
offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleLeftRightOffsetDp, res.getDisplayMetrics())
}
override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
if (fabDiameter == 0f) {
// There is no cutout to draw.
shapePath.lineTo(length, 0f)
return
}
val fabMargin = 0f
val cradleDiameter = fabMargin * 2 + fabDiameter
val cradleRadius = cradleDiameter / 2f
val roundedCornerRadius = 0f
val roundedCornerOffset = interpolation * roundedCornerRadius
val horizontalOffset = 0f
val middle = center + horizontalOffset
// The center offset of the cutout tweens between the vertical offset when attached, and the
// cradleRadius as it becomes detached.
val cradleVerticalOffset = 0f
val verticalOffset =
interpolation * cradleVerticalOffset + (1 - interpolation) * cradleRadius
val verticalOffsetRatio = verticalOffset / cradleRadius
if (verticalOffsetRatio >= 1.0f) {
// Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
// actually above the edge so just draw a straight line.
shapePath.lineTo(length, 0f)
return // Early exit.
}
// Calculate the path of the cutout by calculating the location of two adjacent circles. One
// circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
// not be rounded. The other circle is the cutout.
// Calculate the X distance between the center of the two adjacent circles using pythagorean
// theorem.
val fabCornerSize = -1f
val cornerSize = fabCornerSize * interpolation
val arcOffset = 0f
val distanceBetweenCenters = cradleRadius + roundedCornerOffset
val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
val distanceY = verticalOffset + roundedCornerOffset
val distanceX =
Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble())
.toFloat()
// Calculate the x position of the rounded corner circles.
val leftRoundedCornerCircleX = middle - distanceX
val rightRoundedCornerCircleX = middle + distanceX
// Calculate the arc between the center of the two circles.
val cornerRadiusArcLength =
Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
val cutoutArcOffset = ARC_QUARTER - cornerRadiusArcLength + arcOffset
// Draw the starting line up to the left rounded corner.
shapePath.lineTo( /* x= */leftRoundedCornerCircleX, 0f)
// Draw the arc for the left rounded corner circle. The bounding box is the area around the
// circle's center which is at `(leftRoundedCornerCircleX, roundedCornerOffset)`.
shapePath.addArc( /* left= */
leftRoundedCornerCircleX - roundedCornerOffset, 0f, /* right= */
leftRoundedCornerCircleX + roundedCornerOffset, /* bottom= */
roundedCornerOffset * 2, /* startAngle= */
ANGLE_UP.toFloat(), /* sweepAngle= */
cornerRadiusArcLength
)
// Draw the cutout circle.
shapePath.addArc( /* left= */
middle - (cradleRadius + offset), /* top= */
-cradleRadius - verticalOffset, /* right= */
middle + (cradleRadius + offset), /* bottom= */
cradleRadius - verticalOffset, /* startAngle= */
ANGLE_LEFT - cutoutArcOffset, /* sweepAngle= */
cutoutArcOffset * 2 + ARC_HALF
)
// Draw an arc for the right rounded corner circle. The bounding box is the area around the
// circle's center which is at `(rightRoundedCornerCircleX, roundedCornerOffset)`.
shapePath.addArc( /* left= */
rightRoundedCornerCircleX - roundedCornerOffset, 0f, /* right= */
rightRoundedCornerCircleX + roundedCornerOffset, /* bottom= */
roundedCornerOffset * 2, /* startAngle= */
ANGLE_UP - cornerRadiusArcLength, /* sweepAngle= */
cornerRadiusArcLength
)
// Draw the ending line after the right rounded corner.
shapePath.lineTo( /* x= */length, 0f)
}
companion object {
private const val ARC_QUARTER = 90
private const val ARC_HALF = 180
private const val ANGLE_UP = 270
private const val ANGLE_LEFT = 180
}
}
From the above CutoutCircleEdgeTreatment constructor you can pass the circleDiameterDp which is the circle diameter in dp value (in the above example is set to 70dp so the parent RelativeLayout it should have paddingTop equal to the radius of the Circle which is 70/2 = 35dp) and the circleLeftRightOffsetDp is used to draw the circle with a left/right offset in dp value. Of Course you can modify further the code based on your needs.
Result:
To overlap the semi circle with the fragment hosted
To make the semi circle overlap with the fragment hosted you have to change the order of fragment:nav_host_fragment_activity_main with the RelativeLayout bottomNavigationViewParentRL like in the below sample:
<?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"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black">
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/bottomNavigationViewParentRL"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
<RelativeLayout
android:id="#+id/bottomNavigationViewParentRL"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="35dp"
android:background="#android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:backgroundTint="#color/white"
app:elevation="2dp"
app:labelVisibilityMode="labeled"
app:itemIconSize="25dp"
app:itemIconTint="#color/item_icon_tint_selector"
app:itemTextColor="#color/item_text_color_selector"
app:menu="#menu/bottom_nav_menu" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
And also give in each of your fragments some bottom margin with the same height of the navigation bar to start at the point of semi circle like in the below sample:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="#android:color/transparent"
tools:context=".ui.dashboard.DashboardFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_green_dark"
android:layout_marginBottom="55dp">
<TextView
android:id="#+id/text_dashboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="25dp"
android:textAlignment="center"
android:textColor="#color/black"
android:text="This is dashboard Fragment"
android:textSize="20sp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
</RelativeLayout>
Result:
Another variation of CutoutCircleEdgeTreatment
class CutoutCircleEdgeTreatment(res: Resources, circleDiameterDp: Float, circleLeftRightOffsetDp: Float) : EdgeTreatment() {
private val fabDiameter: Float
private val offset: Float
init {
fabDiameter = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleDiameterDp, res.getDisplayMetrics())
offset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, circleLeftRightOffsetDp, res.getDisplayMetrics())
}
override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
if (fabDiameter == 0f) {
// There is no cutout to draw.
shapePath.lineTo(length, 0f)
return
}
val fabMargin = 0f
val cradleDiameter = fabMargin * 2 + fabDiameter
val cradleRadius = cradleDiameter / 2f
val roundedCornerRadius = 0f
val roundedCornerOffset = interpolation * roundedCornerRadius
val horizontalOffset = 0f
val middle = center + horizontalOffset
// The center offset of the cutout tweens between the vertical offset when attached, and the
// cradleRadius as it becomes detached.
val cradleVerticalOffset = 0f
val verticalOffset =
interpolation * cradleVerticalOffset + (1 - interpolation) * cradleRadius
val verticalOffsetRatio = verticalOffset / cradleRadius
if (verticalOffsetRatio >= 1.0f) {
// Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
// actually above the edge so just draw a straight line.
shapePath.lineTo(length, 0f)
return // Early exit.
}
// Calculate the path of the cutout by calculating the location of two adjacent circles. One
// circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
// not be rounded. The other circle is the cutout.
// Calculate the X distance between the center of the two adjacent circles using pythagorean
// theorem.
val fabCornerSize = -1f
val cornerSize = fabCornerSize * interpolation
val arcOffset = 0f
val distanceBetweenCenters = cradleRadius + roundedCornerOffset
val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
val distanceY = verticalOffset + roundedCornerOffset
val distanceX =
Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble())
.toFloat()
// Calculate the x position of the rounded corner circles.
val leftRoundedCornerCircleX = middle - distanceX
val rightRoundedCornerCircleX = middle + distanceX
// Calculate the arc between the center of the two circles.
val cornerRadiusArcLength =
Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
val cutoutArcOffset = ARC_QUARTER - cornerRadiusArcLength + arcOffset
// Draw the cutout circle.
shapePath.addArc( /* left= */
middle - (cradleRadius + offset), /* top= */
-cradleRadius - verticalOffset, /* right= */
middle + (cradleRadius + offset), /* bottom= */
(cradleRadius - verticalOffset) * 2, /* startAngle= */
ANGLE_LEFT + 20.0f, /* sweepAngle= */
ARC_HALF - 40.0f
)
}
companion object {
private const val ARC_QUARTER = 90
private const val ARC_HALF = 180
private const val ANGLE_UP = 270
private const val ANGLE_LEFT = 180
}
}
Usage:
val materialShapeDrawable = bottomNavigationView.getBackground() as MaterialShapeDrawable
materialShapeDrawable.shapeAppearanceModel = materialShapeDrawable.shapeAppearanceModel
.toBuilder()
.setTopEdge(CutoutCircleEdgeTreatment(resources, 70.toFloat(), 20.toFloat()))
.build()
Result:
HI im currently trying to achieve something similar to the given image.
can anyone guide me through how can I get the look in Kotlin/xml view?
Part I need to design is the Price Tag view.
much appreciated
Well, first of all, you can use any graphic software to draw the text background maybe photoshop or adobe illustrator for vectors. I'm not very good at this software but I created a vector image that is quite similar to what we actually want.
So in your drawables directory create a new drawable file, call it tv_bg.xml, then copy-paste this vector drawable.
tv_bg.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="50dp"
android:autoMirrored="true"
android:viewportWidth="196.739"
android:viewportHeight="101.01">
<path
android:fillColor="#fff"
android:pathData="M196.736,0.01L196.736,99.993L0,99.993s8.6,11.826 15.051,-45.153 93.532,-54.829 93.532,-54.829Z" />
</vector>
After that create a layout file, I called it my_layout.xml.
my_layout.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"
android:layout_width="match_parent"
android:background="#android:color/white"
android:padding="8dp"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="340dp"
android:scaleType="centerCrop"
android:src="#drawable/your_image_placeholder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="$ 15.00"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:background="#drawable/tv_bg"
android:textColor="#android:color/black"
app:layout_constraintBottom_toBottomOf="#+id/imageView"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
And that will be your final result
Of course, you need to improve the TextView background and text style a little to match what you actually want.
If I were to implement it, I would
customize a textView (let's call it, curvedTextView). Perhaps inside onDraw method, I would draw a background that resemblances the carved part (the background of 15,000). Some coordinate geometry would do the trick. The shape lools like a tangent curve, exponential curve, or simply 2 circles with a common tangent... just pick one.
Once you have your view, simply use constraintlayout, imageView, and place the curvedTextView to bottomRignt corner.
I need to write some codes, and do some trials to come up with a sample code. I'll try to update the answer later. Sorry for the inconvenience.
Update
So after writing some codes, here is my curvedTextView
package com.applications.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.res.ResourcesCompat
import ridmik.one.R
class CurvedTextView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatTextView(context, attrs) {
val TAG = "CurvedTextView"
val white = ResourcesCompat.getColor(context.resources, R.color.colorSolidWhite, null)
val transparentColor = ResourcesCompat.getColor(context.resources, R.color.transparent, null)
val paint: Paint = Paint().apply {
this.color = white
this.style = Paint.Style.FILL_AND_STROKE
}
val path: Path = Path()
val paddingVertical: Float = getPxFromDp(context, 10.0).toFloat()
val paddingHorizontal: Float = getPxFromDp(context, 20.0).toFloat()
val delta: Float = getPxFromDp(context, 10.0).toFloat() // random trial and error
init{
}
fun getPxFromDp(context: Context, dp: Double): Double {
return dp * context.resources.displayMetrics.density
}
override fun draw(canvas: Canvas?) {
if(canvas!=null) {
path.moveTo(paddingHorizontal+delta, 0f)
path.cubicTo(
(paddingHorizontal - delta), delta,
delta, height - delta,
0f, height.toFloat()
)
path.lineTo(width.toFloat(), height.toFloat())
path.lineTo(width.toFloat(), 0f)
path.lineTo(paddingHorizontal+delta, 0f)
canvas.drawPath(path, paint)
}
super.draw(canvas)
}
}
Now simply add inside your constraintLayout:
<ImageView
app:layout_constraintBottom_toBottomOf="parent"
android:background="#FF0000"
android:layout_width="match_parent"
android:layout_height="200dp"/>
<com.applications.ui.CurvedTextView
android:id="#+id/curvedTextView"
android:text="$ 15,000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Output:
Now that you have the view, try tweaking the variables to make it precisely like your requirement.
I am trying to draw a rectangle on top of an image in an ImageView. If I specifify (top, left) as (100, 100) and (bottom, right) = (200, 200) I get this:
As you can see it is not a square. How do I get that?
Here is the code for drawing:
private fun drawRectangle(
color: Int,
bitmap: Bitmap
) {
var image_view: ImageView = findViewById(R.id.image)
val tempBitmap =
Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888)
var canvas: Canvas = Canvas(tempBitmap)
val matrix: Matrix = Matrix()
val bitmap_paint: Paint = Paint(Color.TRANSPARENT)
canvas.drawBitmap(bitmap, matrix, bitmap_paint)
var shapeDrawable: ShapeDrawable
// rectangle positions
var left = 100
var top = 100
var right = 200
var bottom = 200
// draw rectangle shape to canvas
shapeDrawable = ShapeDrawable(RectShape())
shapeDrawable.setBounds( left, top, right, bottom)
shapeDrawable.getPaint().setColor(color)
shapeDrawable.getPaint().setStyle(Paint.Style.STROKE)
shapeDrawable.draw(canvas)
image_view.background = BitmapDrawable(getResources(), tempBitmap)
}
and the xml file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter" />
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:textSize="18sp"
android:background="#80000000"
android:textColor="#android:color/white" />
</FrameLayout>
The problem is that you are using ImageView.background property and "match_parent" for your ImageView.
And parent have rectangular shape which you are using to get width and height for your new bitmap and canvas so ImageView also being stretched.
If you change ImageView width and height to "wrap_content" or to specific size it will give you square square.
Better results will be achieved if you use setImageBitmap instead of "background.
eg change line
image_view.background = BitmapDrawable(getResources(), tempBitmap)
to
image_view.setImageBitmap( tempBitmap)
I am working on an Android Kotlin project. I am applying animation on views. Starting from the basics, I am trying to animate an image view from the bottom of the screen to the center of the screen.
I have an XML layout with the following code.
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimaryDark"
tools:context=".MainActivity">
<LinearLayout
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/main_image_logo"
android:src="#drawable/memento_text_logo"
android:layout_width="#dimen/main_logo_image_width"
android:layout_height="wrap_content" />
<TextView
android:textColor="#android:color/white"
android:id="#+id/main_tv_slogan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/main_slogan"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I am animating the logo image translating from the bottom to the center (where it is originally) in the activity with the following code.
private fun animateMainLogo() {
val valueAnimator = ValueAnimator.ofFloat(0f, main_image_logo.y)
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
main_image_logo.translationY = value
}
valueAnimator.interpolator = LinearInterpolator()
valueAnimator.duration = 1000
valueAnimator.start()
}
When I run the code, it is not animating the view. It is just there where it is and static. What is wrong with my code and how can I fix it?
translationY of view in layout is 0. If you want to animate it from bottom to current position - you should change translationY values from some positive value to 0.
private fun animateLogo() {
val translationYFrom = 400f
val translationYTo = 0f
val valueAnimator = ValueAnimator.ofFloat(translationYFrom, translationYTo).apply {
interpolator = LinearInterpolator()
duration = 1000
}
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
main_image_logo?.translationY = value
}
valueAnimator.start()
}
Same thing can be done this way:
private fun animateLogo() {
main_image_logo.translationY = 400f
main_image_logo.animate()
.translationY(0f)
.setInterpolator(LinearInterpolator())
.setStartDelay(1000)
.start()
}
Add this lines to LinearLayout and ConstraintLayout because without them LinearLayout will cut of parts of animated view when it is outside of LinearLayout bounds.
android:clipChildren="false"
android:clipToPadding="false"
Or make main_image_logo direct child of root ConstraintLayout. Here is result:
I have one view "whiteCircle" inside a button which I want to move from it's initial position to the white box "imageSquared" describing an arc and then get back to its initial position.
What I have tried is:
private fun startArcAnimation() {
val path = Path()
val location = IntArray(2)
imageSquared.getLocationOnScreen(location)
path.arcTo(
0f,
0f,
location[0].toFloat() + imageSquared.width,
location[0].toFloat() + imageSquared.height,
180f,
180f,
true
)
val animator = ObjectAnimator.ofFloat(whiteCircle, View.X, View.Y, path)
animator.duration = 1000
animator.start()
}
And this is the result:
Can you help me setting path values correct?
I have been struggling with arcTo properties without success.
Thanks in advance.
Here is my implementation:
arcTo method create oval which is placed into rect. So first of all you need to create correct rect. Your path should start from 180 degree in oval and move 180 degrees clockwise (Zero angle of oval is in right side).
I suggest to animate translationX and translationY properties of view.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
greenButton.setOnClickListener {
startArcAnimation()
}
}
private fun startArcAnimation() {
if (red.translationX != 0f) {
//return to start position animation
red.animate().translationX(0f).translationY(0f).start()
return
}
val rectHeight = 600
val left = 0f
val top = -rectHeight / 2
val right = white.x - red.x
val bottom = white.y + rectHeight / 2 - red.y
val path = Path()
path.arcTo(left, top.toFloat(), right, bottom, 180f, 180f, true)
val animator = ObjectAnimator.ofFloat(red, View.TRANSLATION_X, View.TRANSLATION_Y, path)
animator.duration = 1000
animator.start()
}
}
Here is activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
android:padding="16dp">
<Button
android:id="#+id/greenButton"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="#0a0"
android:stateListAnimator="#null" />
<ImageView
android:id="#+id/white"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical|right"
android:background="#fff" />
<ImageView
android:id="#+id/red"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="70dp"
android:layout_marginBottom="10dp"
android:background="#f00" />
</FrameLayout>