i am beginner level android developer and i know basics of layouts and views.Now i want to go one step ahead and want to learn interactive designs like this image
https://ufile.io/uas4ljkr
In this image we have red area and white area and both are separated with a wave like shape not simple shape which we see in all layouts.Any help regarding this will be appreciated.Thanks
A good way of accomplishing this is to just to create your own custom view and override the onDraw() method.
https://developer.android.com/training/custom-views/custom-drawing
I did simple quick and dirty custom drawing for you that is similar to example you gave
MainActivity
class MainActivity : AppCompatActivity() {
private var waveView: WaveView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val wave = WaveView(this)
waveView = wave
setContentView(wave)
}
}
WaveView
class WaveView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint()
private val path = Path()
init {
paint.style = Paint.Style.FILL
paint.isAntiAlias = true
paint.isDither = true
}
override fun onDraw(canvas: Canvas?) {
val point1X = width * 0.0f
val point1Y = height * 0.5f
val point2X = width * 0.35f
val point2Y = height * 0.45f
val point3X = width * 0.5f
val point3Y = height * 0.3f
val point4X = width * 0.75f
val point4Y = height * 0.1f
val point5Y = height * 0.25f
paint.color = Color.RED
canvas?.drawColor(Color.WHITE)
path.moveTo(0f, 0f)
path.lineTo(point1X, point1Y)
path.quadTo(point2X, point2Y, point3X, point3Y)
path.lineTo(width.toFloat(), point3Y)
path.lineTo(width.toFloat(), 0f)
canvas?.drawPath(path, paint)
path.reset()
paint.color = Color.WHITE
path.moveTo(point3X, height.toFloat())
path.lineTo(point3X, point3Y)
path.quadTo(point4X, point4Y, width.toFloat(), point5Y)
path.lineTo(width.toFloat(), height.toFloat())
canvas?.drawPath(path, paint)
}
}
custom wave
Hopefully this points you in the right direction.
Related
I have a custom view named FloorView. I want this Floor view to be occupying the whole screen, and I need this dynamically (maybe match_parent). I don't want to hard code the size of the view.
I've tried doing this in FloorView.kt
private val rectangle = RectF(
ViewGroup.LayoutParams.MATCH_PARENT.toFloat(),
ViewGroup.LayoutParams.MATCH_PARENT.toFloat(), 0f, 0f)
I was thinking I could use the match_parent attribute, and match the ConstraintLayout of my MainActivity, but that didn't work, nothing was drawn, possibly because the ConstraintLayout doesn't exist yet
My code for the FloorView.kt is this
class FloorView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL_AND_STROKE
color = Color.BLACK
}
private val rectangle = RectF(
ViewGroup.LayoutParams.MATCH_PARENT.toFloat(),
ViewGroup.LayoutParams.MATCH_PARENT.toFloat(), 0f, 0f)
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas!!.drawRect(rectangle, paint)
}
}
How can I make a custom view occupy all available screen space? (like match_parent)
You can do something like:
class FloorView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val displayMetrics by lazy { Resources.getSystem().displayMetrics }
private val deviceWidth by lazy { (displayMetrics.widthPixels).toFloat() }
private val deviceHeight by lazy { (displayMetrics.heightPixels).toFloat() }
// you can also use this.width & this.Height if this view already
// added in a ViewGroup via XML
private val rectangle by lazy { RectF(0f, 0f, deviceWidth, deviceHeight) }
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL_AND_STROKE
color = Color.BLACK
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas!!.drawRect(rectangle, paint)
}
}
Hi guys meniman98 doesn't want a view of "screen size", just want the view to match parent view's size. Actually "screen size" is impossible -- a view can not exceed its parent ViewGroup's size.
The question is in
private val rectangle = RectF(
ViewGroup.LayoutParams.MATCH_PARENT.toFloat(),
ViewGroup.LayoutParams.MATCH_PARENT.toFloat(), 0f, 0f)
this will not get expected size because MTACH_PARENT is just a const int to instruct the layout planner, not size.
View's size is first decided in view's onSizeChanged call, so you should override this function and create the rectangle there.
private var rectangle:RectF? = null
override fun onSizeChanged(int w, int h, int oldW, int oldH) {
rectangle = RectF(0f, 0f, w.toFloat(), h.toFloat())
}
In Android app , I want to draw a button in canvas , like the image exactly the below image
I tried some stack overflow answers but can't get the exact output , particularly the light shadow below . My worst code is below
val corners = floatArrayOf(
80f, 80f, // Top left radius in px
80f, 80f, // Top right radius in px
0f, 0f, // Bottom right radius in px
0f, 0f // Bottom left radius in px
)
val path = Path()
val rect = RectF(550f, 500f, 100f, 300f)
paint.style = Paint.Style.FILL;
paint.color = Color.WHITE;
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas?.drawPath(path, paint)
paint.style = Paint.Style.STROKE;
paint.color = Color.BLACK;
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas?.drawPath(path, paint)
paint.setColor(Color.RED)
paint.setStyle(Paint.Style.FILL)
val paint2 = Paint()
paint2.setColor(Color.GREEN)
paint2.setTextSize(50f) //set text size
val w: Float = paint2.measureText("VIEW CAMPSITE MAP") / 2
val textSize: Float = paint2.textSize
canvas?.drawText("VIEW CAMPSITE MAP", 300f, 300f ,paint2);
Also I can't set text in correct position
I also need a gradient to show bulging state like a real material button
And also need an click listener in the canvas / paint
I need it in pure canvas and paint not in any views
please help me
Here is one of the ways. Dont forget to move all variables like text, textSize, etc in custom attributes. Also I didnt get what do you mean by "click listener in the canvas"
class CustomView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
//constants. move them all to custom attributes
private val boxRadius = convertDpToPixel(10f)
private val boxColor = Color.parseColor("#52618e")
private val boxBackgroundColor = Color.WHITE
private val boxShadowSize = convertDpToPixel(2f)
private val boxStrokeWidth = convertDpToPixel(1f)
private val textColor = Color.parseColor("#21a207")
private val fontSize = convertDpToPixel(30f)
private val text = "View Campsite Plan"
private lateinit var boxShadow: RectF
private lateinit var boxBackground: RectF
private lateinit var boxShadowPaint: Paint
private lateinit var boxBackgroundPaint: Paint
private var textWidth = 0f
private var textSmallGlyphHeight = 0f
private lateinit var textPaint: Paint
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
boxShadow = RectF(0f, 0f, w.toFloat(), h.toFloat())
boxBackground = RectF(boxStrokeWidth, boxStrokeWidth,
w.toFloat()-boxStrokeWidth, h.toFloat()-boxStrokeWidth-boxShadowSize)
boxShadowPaint = Paint().apply { color = boxColor }
boxBackgroundPaint = Paint().apply { color = boxBackgroundColor }
textPaint = Paint().apply {
color = textColor
textSize = fontSize
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
textWidth = measureText(text)
textSmallGlyphHeight = fontMetrics.run { ascent + descent }
}
}
override fun onDraw(canvas: Canvas?) {
canvas?.drawRoundRect(boxShadow, boxRadius, boxRadius, boxShadowPaint)
canvas?.drawRoundRect(boxBackground, boxRadius, boxRadius, boxBackgroundPaint)
val textStartPadding = (width - textWidth)/2f
val textTopPadding = (height - textSmallGlyphHeight)/2f
canvas?.drawText(text, textStartPadding, textTopPadding, textPaint)
}
private fun convertDpToPixel(dp: Float) =
dp*(resources.displayMetrics.densityDpi/DisplayMetrics.DENSITY_DEFAULT)
}
I have been trying to create a custom view similar to that shown below in which the white 'wave' view contains an extended 'inverse rounded corner' in the top right and a rounded corner in the bottom left.
I had attempted to achieve this using the Material Shape themeing but this doesn't seem to support the 'inverse' rounded corner.
To achieve this, I have been using a View and custom drawing within its Canvas, but have not been able to get it working, as I am unsure how to achieve the inverse rounded corner effect.
Any help or guidance would be greatly appreciated
class TestView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var mPath = Path()
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val h = height.toFloat()
val h2 = height.toFloat() / 2f
val w = width.toFloat()
val w2 = width.toFloat() / 2f
mPath.reset()
mPath.addArc(w2, 0f, w, h2, 0f, 90f)
mPath.addArc(0f, h2, w2, h, 180f, 90f)
mPath.lineTo(w, h2)
mPath.lineTo(w, h)
mPath.lineTo(0f, h)
mPath.close()
mPath.fillType = Path.FillType.WINDING
canvas?.clipPath(mPath)
canvas?.drawColor(Color.DKGRAY)
}
}
I solved this by using Bezier curves and adapting the answer found in this SO question
The code I used to achieve this was as follows:
class SweepView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint().apply {
color = Color.RED
isAntiAlias = true
style = Paint.Style.FILL
}
private val path = Path()
override fun onDraw(canvas: Canvas?) {
val h = height.toFloat()
val halfH = h / 2f
val w = width.toFloat()
val delta = width / 3f
path.reset()
path.moveTo(0f, h)
path.cubicTo(0f, halfH, 0f, halfH, delta, halfH)
path.lineTo(w - delta, halfH)
path.cubicTo(w, halfH, w, halfH, w, 0f)
path.lineTo(w, h)
path.close()
canvas?.drawPath(path, paint)
}
}
I want to have an ImageView containing an Image with rounded curves, but not circular (which is way easier of course)..
The X marked areas should be black, the rest should stay blue. It should look (kind of) like this
What I tried:
I spent hours fighting with Path.quadTo and Path.cubicTo with the help of some tools but I didn't have any success yet. I just don't really get the usage to be honest.
What my code currently looks like:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint()
paint.color = Color.BLACK
paint.strokeWidth = 1f
paint.strokeCap = Paint.Cap.ROUND
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
paint.style = Paint.Style.FILL
val fHeight = canvas.height.toFloat()
val startEndHeight = canvas.height / 1.18f
val fWidth = canvas.width.toFloat()
val halfWidth = (fWidth / 2)
val path = Path()
//X = Left side, Y = close to bottom
val ptStart = PointF(0f, startEndHeight)
//X = Middle, Y = Bottom
val ptMiddle = PointF(halfWidth, fHeight + 95)
// X = Right Side, Y = close to bottom
val ptEnd = PointF(fWidth, startEndHeight)
path.moveTo(ptStart.x, ptStart.y)
path.quadTo(ptMiddle.x, ptMiddle.y, ptEnd.x, ptEnd.y)
path.close()
canvas.drawPath(path, paint)
}
It can't be that hard, right?
Is it possible to colorize the red marked areas and leave everything else untouched?
Fixed! This is my final solution which applies a quad bezier to the ImageView. I had to add 2 lines to the path to get the result I wanted.
class HeaderImageView : AppCompatImageView {
constructor(context: Context?) : super(context) {
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init()
}
lateinit var paint: Paint
private fun init() {
paint = Paint()
paint.color = Color.WHITE
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
paint.style = Paint.Style.FILL
}
#SuppressLint("CanvasSize")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val fHeight = canvas.height.toFloat()
val startEndHeight = canvas.height / 1.18f
val fWidth = canvas.width.toFloat()
val halfWidth = (fWidth / 2)
val path = Path()
//X = Left side, Y = close to bottom
val ptStart = PointF(0f, startEndHeight)
//X = Middle, Y = Bottom
val ptMiddle = PointF(halfWidth, fHeight + 95)
// X = Right Side, Y = close to bottom
val ptEnd = PointF(fWidth, startEndHeight)
path.moveTo(ptStart.x, ptStart.y)
path.quadTo(ptMiddle.x, ptMiddle.y, ptEnd.x, ptEnd.y)
path.lineTo(fWidth, fHeight)
path.lineTo(0f, fHeight)
path.close()
canvas.drawPath(path, paint)
}
}
Can anyone help me out on how I going to draw semicircle of the picture below with canvas and also how to detect the collection of the drawing object?
I had try to draw it using XML and i dont know how to detect the collision of it. I just want to detect the collision of the black part but not the whole circle.
Thank you.
I've just done something similar, with a custom View:
class SemiCircleView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint: Paint = Paint()
private var rectangle: RectF? = null
private var margin: Float
init {
paint.isAntiAlias = true
paint.color = ContextCompat.getColor(context, R.color.colorAquamarine)
paint.style = Paint.Style.STROKE
paint.strokeWidth = 5.dpToPx()
margin = 3.dpToPx() // margin should be >= strokeWidth / 2 (otherwise the arc is cut)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (rectangle == null) {
rectangle = RectF(0f + margin, 0f + margin, width.toFloat() - margin, height.toFloat() - margin)
}
canvas.drawArc(rectangle!!, 90f, 180f, false, paint)
}
}
Add it you your XML layout like this:
<com.example.view.SemiCircleView
android:layout_width="120dp"
android:layout_height="120dp"/>
That's the result:
In my case I want to programmatically control the % of the circle being drawn, so I've added a method setArcProportion that controls that:
class SemiCircleView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val mainPaint: Paint = Paint()
private val backgroundPaint: Paint = Paint()
private var rectangle: RectF? = null
private var margin: Float
private var arcProportion: Float = 0f
init {
mainPaint.isAntiAlias = true
mainPaint.color = ContextCompat.getColor(context, R.color.colorLutea)
mainPaint.style = Paint.Style.STROKE
mainPaint.strokeWidth = 5.dpToPx()
backgroundPaint.isAntiAlias = true
backgroundPaint.color = ContextCompat.getColor(context, R.color.black_08)
backgroundPaint.style = Paint.Style.STROKE
backgroundPaint.strokeWidth = 5.dpToPx()
margin = 3.dpToPx() // margin should be >= strokeWidth / 2 (otherwise the arc is cut)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (rectangle == null) {
rectangle = RectF(0f + margin, 0f + margin, width.toFloat() - margin, height.toFloat() - margin)
}
canvas.drawArc(rectangle!!, -90f, arcProportion * 360, false, mainPaint)
// This 2nd arc completes the circle. Remove it if you don't want it
canvas.drawArc(rectangle!!, -90f + arcProportion * 360, (1 - arcProportion) * 360, false, backgroundPaint)
}
/**
* #param arcProportion The proportion of the semi circle arc, from 0 to 1. Setting 0 makes the arc invisible, and 1
* makes a whole circle.
*/
fun setArcProportion(arcProportion: Float) {
this.arcProportion = arcProportion
invalidate()
}
}
So if I do semiCircleView.setArcProportion(0.62f) I have:
Bonus - To make the arc grow with an animation modify setArcProportion like this:
private const val ANIMATION_BASE_DURATION_MS: Long = 500 // milliseconds
fun setArcProportion(arcProportion: Float) {
ValueAnimator.ofFloat(0f, arcProportion).apply {
interpolator = DecelerateInterpolator()
// The animation duration is longer for a larger arc
duration = ANIMATION_BASE_DURATION_MS + (arcProportion * ANIMATION_BASE_DURATION_MS).toLong()
addUpdateListener { animator ->
this#SemiCircleView.arcProportion = animator.animatedValue as Float
this#SemiCircleView.invalidate()
}
start()
}
}
In your onDraw method draw a circle .
canvas.drawCircle(x, y, 10, paint);
Now draw a rect with your background colour
Paint fillBackgroundPaint = new Paint();
fillBackgroundPaint.setAntiAlias(true);
fillBackgroundPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(x,y+10,x+10,y-10);
This should serve the purpose.