Related
I am trying to do a circular progress bar where the progress bar consists of solid color and a red dot. See the image below. Basically it is a timer where the red dot "eats" the white color.
This is what I am trying to achieve:
Here is the code that I am using from other StackOverflow questions
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.5"
android:shape="ring"
android:thickness="1dp"
android:useLevel="true">
<gradient
android:angle="0"
android:endColor="#FFFFFF"
android:startColor="#FFFFFF"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
With this code I can get the white ring but how can I get the red dot moving with it (using XML)
Thank you
PS: This is not a duplicate question as I didn't see a question that discusses progress bar having 2 different types of drawables.
you can create custom Progress View by Extends View Class and override onDraw() method
to draw semi circle you can use drawArc()
and use ValueAnimator to change angle of Arc
Code:
TimerView.java
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.support.v4.math.MathUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import java.util.concurrent.TimeUnit;
public class TimerView extends View {
private Paint ProgressPaint,indicatorPaint;
private float mProgressValue;
private ValueAnimator mTimerAnimator;
float stratAngel=270;
private int strokeWidth=10;
public TimerView(Context context) {
super(context);
ini();
}
public TimerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
ini();
}
public TimerView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ini();
}
void ini(){
ProgressPaint =new Paint();
ProgressPaint.setAntiAlias(true);
ProgressPaint.setStyle(Paint.Style.STROKE);
ProgressPaint.setStrokeWidth(strokeWidth);
ProgressPaint.setColor(Color.WHITE);
indicatorPaint=new Paint();
indicatorPaint.setAntiAlias(true);
indicatorPaint.setStyle(Paint.Style.FILL);
indicatorPaint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float w = (getWidth() - getPaddingLeft() - getPaddingRight())/2;
float h = (getHeight() - getPaddingTop() - getPaddingBottom())/2;
float radius = Math.min(w, h) / 2.0f;
PointF center = new PointF(getLeft() + radius, getTop() + radius);
RectF rect = new RectF(center.x - radius, center.y - radius,
center.x + radius, center.y + radius);
float progressAngel=360 * mProgressValue;
canvas.drawArc(rect, stratAngel, progressAngel, false, ProgressPaint);
float xPos = radius * (float)Math.cos(Math.toRadians(stratAngel+progressAngel)) + center.x;
float yPos = radius * (float)Math.sin(Math.toRadians(stratAngel+progressAngel)) + center.y;
canvas.drawCircle(xPos, yPos, 30, indicatorPaint);
}
public void start(int secs) {
stop();
mTimerAnimator = ValueAnimator.ofFloat(1f, 0f);
mTimerAnimator.setDuration(TimeUnit.SECONDS.toMillis(secs));
mTimerAnimator.setInterpolator(new LinearInterpolator());
mTimerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
mProgressValue=(float) animation.getAnimatedValue();
invalidate();
}
});
mTimerAnimator.setRepeatCount(Integer.MAX_VALUE);
mTimerAnimator.start();
}
public void stop() {
if (mTimerAnimator != null && mTimerAnimator.isRunning()) {
mTimerAnimator.cancel();
mTimerAnimator = null;
mProgressValue=0.0f;
}
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
start(5);
}
}
and use it in xml layout like that:
<LinearLayout 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"
android:orientation="horizontal"
android:background="#dedede"
android:padding="16dp"
>
<<your package name>.TimerView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
final Result :screenshot
hope it Helps
You should use progress bar. Add progress bar view to your code then add background XML file like below code :
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="120"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="140">
<item android:id="#android:id/background">
<shape
android:innerRadiusRatio="2.6"
android:shape="ring"
android:useLevel="false"
android:angle="0"
android:type="sweep"
android:thicknessRatio="80.0">
<solid android:color="#d3d3d3"/>
</shape>
</item>
<item android:id="#+id/rate_progress">
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.78"
android:shape="ring"
android:angle="0"
android:type="sweep"
android:thicknessRatio="15.0">
<solid android:color="#color/colorAccent"/>
</shape>
</rotate>
</item>
</layer-list>
set below background to your progress bar . below code is my example :
LayerDrawable layerDrawable = (LayerDrawable) progressBar.getContext().getResources()
.getDrawable(R.drawable.rate_background);
RotateDrawable progressDrawable = (RotateDrawable) layerDrawable
.findDrawableByLayerId(R.id.rate_progress);
GradientDrawable gradientDrawable = (GradientDrawable) progressDrawable.getDrawable();
if (gradientDrawable != null) {
gradientDrawable.setColor(Color.parseColor(color));
progressBar.setProgressDrawable(layerDrawable);
}
progressBar.setProgress(rate);
And add below style to your progress bar
style="?android:attr/progressBarStyleHorizontal"
Is there a way that I can specify a triangle shape in an XML file?
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="triangle">
<stroke android:width="1dip" android:color="#FFF" />
<solid android:color="#FFF" />
</shape>
Can we do this with a path shape or something? I just need an equilateral triangle.
Thanks
In this post I describe how to do it. And here is the XML defining triangle:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="87%" >
<shape
android:shape="rectangle" >
<stroke android:color="#color/transparent" android:width="10dp"/>
<solid
android:color="#color/your_color_here" />
</shape>
</rotate>
</item>
</layer-list>
Refer to my post if something is unclear or you need explanation how it is built. It is rotated an cutout rectangle :) it is very smart and well working solution.
EDIT:
to create an arrow pointing like --> use:
...
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="13%"
android:pivotY="-40%" >
...
And to create an arrow pointing like <-- use:
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="87%"
android:pivotY="140%" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="▼"/>
You can get here more options.
You can use vector to make triangle like this
ic_triangle_right.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:pathData="M0,12l0,12 11.5,-5.7c6.3,-3.2 11.5,-6 11.5,-6.3 0,-0.3 -5.2,-3.1 -11.5,-6.3l-11.5,-5.7 0,12z"
android:strokeColor="#00000000"
android:fillColor="#000000"/>
</vector>
Then use it like
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_triangle_right"
/>
For change the color and direction, use android:tint and android:rotation
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_triangle_right"
android:rotation="180" // change direction
android:tint="#00f" // change color
/>
Result
For change the shape of vector, you can change the width/height of vector. Example change width to 10dp
<vector
android:width="10dp"
android:height="24dp"
>
...
</vector>
You can use vector drawables.
If your minimum API is lower than 21, Android Studio automatically creates PNG bitmaps for those lower versions at build time (see Vector Asset Studio). If you use the support library, Android even manages "real vectors" down to API 7 (more on that in the update of this post at the bottom).
A red upwards pointing triangle would be:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="100dp"
android:width="100dp"
android:viewportHeight="100"
android:viewportWidth="100" >
<group
android:name="triableGroup">
<path
android:name="triangle"
android:fillColor="#FF0000"
android:pathData="m 50,0 l 50,100 -100,0 z" />
</group>
</vector>
Add it to your layout and remember to set clipChildren="false" if you rotate the triangle.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<ImageView
android:layout_width="130dp"
android:layout_height="100dp"
android:rotation="0"
android:layout_centerInParent="true"
android:background="#drawable/triangle"/>
</RelativeLayout>
Change the size (width/height) of the triangle by setting the Views layout_width/layout_height attributes. This way you can also get an eqilateral triagle if you do the math correct.
UPDATE 25.11.2017
If you use the support library you can use real vectors (instead if bitmap creation) as far back as API 7. Simply add:
vectorDrawables.useSupportLibrary = true
do your defaultConfig in your module's build.gradle.
Then set the (vector xml) drawable like this:
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:srcCompat="#drawable/triangle" />
Everything is documented very nicely on the Vector Asset Studio page.
Ever since this feature I've been working entirely without bitmaps in terms of icons. This also reduces APK size quite a bit.
The solution of Jacek Milewski works for me and, based on his solution, if you need and inversed triangle you can use this:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="135%"
android:pivotY="15%">
<shape android:shape="rectangle">
<solid android:color="#color/aquamarine" />
</shape>
</rotate>
</item>
</layer-list>
I would definetely go for implementing a View in this case:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
public class TriangleShapeView extends View {
public TriangleShapeView(Context context) {
super(context);
}
public TriangleShapeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TriangleShapeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth() / 2;
Path path = new Path();
path.moveTo( w, 0);
path.lineTo( 2 * w , 0);
path.lineTo( 2 * w , w);
path.lineTo( w , 0);
path.close();
Paint p = new Paint();
p.setColor( Color.RED );
canvas.drawPath(path, p);
}
}
Make use of it in your layouts as follows:
<TriangleShapeView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff487fff">
</TriangleShapeView>
Using this implementation will give you the following result:
See answer here:
Custom arrows without image: Android
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="24dp"
android:viewportWidth="32.0"
android:viewportHeight="24.0">
<path android:fillColor="#e4e4e8"
android:pathData="M0 0 h32 l-16 24 Z"/>
</vector>
Using vector drawable:
<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:pathData="M0,0 L24,0 L0,24 z"
android:strokeColor="#color/color"
android:fillColor="#color/color"/>
</vector>
May I help you without using XML ?
Simply,
Custom Layout ( Slice ) :
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
public class Slice extends View {
Paint mPaint;
Path mPath;
public enum Direction {
NORTH, SOUTH, EAST, WEST
}
public Slice(Context context) {
super(context);
create();
}
public Slice(Context context, AttributeSet attrs) {
super(context, attrs);
create();
}
public void setColor(int color) {
mPaint.setColor(color);
invalidate();
}
private void create() {
mPaint = new Paint();
mPaint.setStyle(Style.FILL);
mPaint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
mPath = calculate(Direction.SOUTH);
canvas.drawPath(mPath, mPaint);
}
private Path calculate(Direction direction) {
Point p1 = new Point();
p1.x = 0;
p1.y = 0;
Point p2 = null, p3 = null;
int width = getWidth();
if (direction == Direction.NORTH) {
p2 = new Point(p1.x + width, p1.y);
p3 = new Point(p1.x + (width / 2), p1.y - width);
} else if (direction == Direction.SOUTH) {
p2 = new Point(p1.x + width, p1.y);
p3 = new Point(p1.x + (width / 2), p1.y + width);
} else if (direction == Direction.EAST) {
p2 = new Point(p1.x, p1.y + width);
p3 = new Point(p1.x - width, p1.y + (width / 2));
} else if (direction == Direction.WEST) {
p2 = new Point(p1.x, p1.y + width);
p3 = new Point(p1.x + width, p1.y + (width / 2));
}
Path path = new Path();
path.moveTo(p1.x, p1.y);
path.lineTo(p2.x, p2.y);
path.lineTo(p3.x, p3.y);
return path;
}
}
Your Activity ( Example ) :
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
public class Layout extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Slice mySlice = new Slice(getApplicationContext());
mySlice.setBackgroundColor(Color.WHITE);
setContentView(mySlice, new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
}
}
Working Example :
Another absolutely simple Calculate function you may interested in ..
private Path Calculate(Point A, Point B, Point C) {
Path Pencil = new Path();
Pencil.moveTo(A.x, A.y);
Pencil.lineTo(B.x, B.y);
Pencil.lineTo(C.x, C.y);
return Pencil;
}
You can add following triangle in background using following xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="100dp"
android:width="100dp"
android:viewportHeight="100"
android:viewportWidth="100" >
<group
android:name="triableGroup">
<path
android:name="triangle"
android:fillColor="#848af8"
android:pathData="M 0,20 L 0,0 L 100,0 L 100,20 L 54,55 l -1,0.6 l -1,0.4 l -1,0.2 l -1,0 l -1,-0 l -1,-0.2 l -1,-0.4 l -1,-0.6 L 46,55 L 0,20 -100,-100 Z" />
</group>
</vector>
The whole logic to customize xml design is in pathData. Consider top-left as (0,0) and design the layout as per your requirement.
Check this answer.
For those who want a right triangle arrow, here you go:
STEP 1: Create a drawable XML file, copy and paste the following XML content into your drawable XML. (Please be informed that you can use any name for your drawable XML file. For my case, I name it "v_right_arrow")
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<rotate
android:fromDegrees="45"
android:toDegrees="-45"
android:pivotX="15%"
android:pivotY="-36%" >
<shape
android:shape="rectangle" >
<stroke android:color="#android:color/transparent" android:width="1dp"/>
<solid
android:color="#000000" />
</shape>
</rotate>
</item>
</layer-list>
STEP 2: In your layout's XML, create a View and bind its background to the drawable XML that you have just created in STEP 1. For my case, I bind v_right_arrow to my View's background property.
<View
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#drawable/v_right_arrow">
</View>
Sample output:
Hope this helps, good luck!
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="87%" >
<shape
android:shape="rectangle" >
<stroke android:color="#android:color/transparent" android:width="0dp"/>
<solid
android:color="#fff" />
</shape>
</rotate>
</item>
</layer-list>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="45"
android:pivotX="135%"
android:pivotY="1%"
android:toDegrees="45">
<shape android:shape="rectangle">
<stroke
android:width="-60dp"
android:color="#android:color/transparent" />
<solid android:color="#color/orange" />
</shape>
</rotate>
</item>
</layer-list>
I try to create triangle image like back button of android navigation bar but I haven't found any solution.
Now, I found the solution with myself and would like to share it.
use xml below
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="20dp"
android:left="480dp"
android:right="60dp"
android:top="20dp">
<shape>
<size android:width="60dp" />
<corners android:radius="80dp"/>
<solid android:color="#AAA" />
</shape>
</item>
<item
android:bottom="480dp"
android:right="70dp"
android:top="20dp">
<rotate
android:fromDegrees="-28"
android:pivotX="96%"
android:pivotY="50%"
android:toDegrees="-20">
<shape>
<corners android:radius="80dp"/>
<size android:height="60dp" />
<solid android:color="#AAA" />
</shape>
</rotate>
</item>
<item
android:bottom="20dp"
android:right="70dp"
android:top="480dp">
<rotate
android:fromDegrees="28"
android:pivotX="96%"
android:pivotY="50%"
android:toDegrees="-20">
<shape>
<corners android:radius="80dp"/>
<size android:height="60dp" />
<solid android:color="#AAA" />
</shape>
</rotate>
</item>
I have never done this, but from what I understand you can use the PathShape class:
http://developer.android.com/reference/android/graphics/drawable/shapes/PathShape.html
I my be late to party and I came across same problem and Google pointed me to this StackOverflow thread as first result.
I tried using xml way to add triangle and find out a problem that the triangle
shape via xml approach is taking more space than it appears.
See screen shot with layout bounds on
So ended up making this custom view class which can draws Triangle of any of following types:-
up pointing
down pointing
left pointing &
right pointing
asas
package com.hiteshsahu.materialupvotewidget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
public class TriangleShapeView extends View {
private int colorCode = Color.DKGRAY;
public int getColorCode() {
return colorCode;
}
public void setColorCode(int colorCode) {
this.colorCode = colorCode;
}
public TriangleShapeView(Context context) {
super(context);
if (isInEditMode())
return;
}
public TriangleShapeView(Context context, AttributeSet attrs) {
super(context, attrs);
if (isInEditMode())
return;
}
public TriangleShapeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode())
return;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth() / 2;
int h = getHeight() / 2;
//Choose what type of triangle you want here
Path path = getLeftTriangle(w, h);
path.close();
Paint p = new Paint();
p.setColor(colorCode);
p.setAntiAlias(true);
canvas.drawPath(path, p);
}
#NonNull
/**
* Return Path for down facing triangle
*/
private Path getInvertedTriangle(int w, int h) {
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(w, 2 * h);
path.lineTo(2 * w, 0);
path.lineTo(0, 0);
return path;
}
#NonNull
/**
* Return Path for Up facing triangle
*/
private Path getUpTriangle(int w, int h) {
Path path = new Path();
path.moveTo(0, 2 * h);
path.lineTo(w, 0);
path.lineTo(2 * w, 2 * h);
path.lineTo(0, 2 * h);
return path;
}
#NonNull
/**
* Return Path for Right pointing triangle
*/
private Path getRightTriangle(int w, int h) {
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(2 * w, h);
path.lineTo(0, 2 * h);
path.lineTo(0, 0);
return path;
}
#NonNull
/**
* Return Path for Left pointing triangle
*/
private Path getLeftTriangle(int w, int h) {
Path path = new Path();
path.moveTo(2 * w, 0);
path.lineTo(0, h);
path.lineTo(2 * w, 2 * h);
path.lineTo(2 * w, 0);
return path;
}
}
You can Simply use it in xml layout like this
<com.hiteshsahu.materialupvote.TriangleShapeView
android:layout_width="50dp"
android:layout_height="50dp"></com.hiteshsahu.materialupvote.TriangleShapeView>
I know OP want solutions in xml solution but as I pointed out problem with xml approach . I Hope it might help somebody.
Using the solution of Jacek Milewski I made an oriented down angle with a transparent background.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="135"
android:pivotX="65%"
android:pivotY="20%"
android:toDegrees="135"
>
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#color/blue"
/>
<solid android:color="#color/transparent" />
</shape>
</rotate>
</item>
</layer-list>
You can change android:pivotX and android:pivotY to shift the angle.
Usage:
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:src="#drawable/ic_angle_down"
/>
Parameters depend on the size of the image. For instance, if ImageView has size 100dp*80dp, you should use these constants:
<rotate
android:fromDegrees="135"
android:pivotX="64.5%"
android:pivotY="19%"
android:toDegrees="135"
>
I provide this customView below if you don't want to hack xml.
Please have a try.
/**
* TriangleView
*
* #author Veer
* #date 2020-09-03
*/
class TriangleView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var triangleColor: Int = 0
private var direction = Direction.Bottom
private val paint by lazy {
Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = triangleColor
}
}
init {
initStyle(context, attrs, defStyleAttr)
}
private fun initStyle(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.TriangleView, defStyleAttr, 0)
with(ta) {
triangleColor =
getColor(R.styleable.TriangleView_triangle_background, Color.parseColor("#000000"))
val directionValue =
getInt(R.styleable.TriangleView_triangle_direction, Direction.Bottom.value)
direction = when (directionValue) {
Direction.Top.value -> Direction.Top
Direction.Bottom.value -> Direction.Bottom
Direction.Left.value -> Direction.Left
Direction.Right.value -> Direction.Right
else -> Direction.Bottom
}
recycle()
}
}
override fun onDraw(canvas: Canvas) {
calculatePath(direction).let {
canvas.drawPath(it, paint)
}
}
private fun calculatePath(direction: Direction): Path {
var p1: Point? = null
var p2: Point? = null
var p3: Point? = null
val width = width
val height = height
when (direction) {
Direction.Top -> {
p1 = Point(0, height)
p2 = Point(width / 2, 0)
p3 = Point(width, height)
}
Direction.Bottom -> {
p1 = Point(0, 0)
p2 = Point(width / 2, height)
p3 = Point(width, 0)
}
Direction.Left -> {
p1 = Point(width, 0)
p2 = Point(0, height / 2)
p3 = Point(width, height)
}
Direction.Right -> {
p1 = Point(0, 0)
p2 = Point(width, height / 2)
p3 = Point(0, height)
}
}
val path = Path()
path.moveTo(p1.x.toFloat(), p1.y.toFloat())
path.lineTo(p2.x.toFloat(), p2.y.toFloat())
path.lineTo(p3.x.toFloat(), p3.y.toFloat())
return path
}
private enum class Direction(val value: Int) {
Top(0),
Bottom(1),
Left(2),
Right(3)
}
}
<declare-styleable name="TriangleView">
<attr name="triangle_direction" format="enum">
<enum name="top" value="0" />
<enum name="bottom" value="1" />
<enum name="left" value="2" />
<enum name="right" value="3" />
</attr>
<attr name="triangle_background" format="reference|color" />
</declare-styleable>
<LinearLayout
android:id="#+id/ly_fill_color_shape"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:background="#drawable/shape_triangle"
android:gravity="center"
android:orientation="horizontal" >
</LinearLayout>
<item>
<rotate
android:fromDegrees="45"
android:pivotX="-15%"
android:pivotY="77%"
android:toDegrees="45" >
<shape android:shape="rectangle" >
<stroke
android:width="2dp"
android:color="#color/black_color" />
<solid android:color="#color/white" />
<padding android:left="1dp" />
</shape>
</rotate>
</item>
<item android:top="200dp">
<shape android:shape="line" >
<stroke
android:width="1dp"
android:color="#color/black_color" />
<solid android:color="#color/white" />
</shape>
</item>
Google provides a Equilateral triangle here.
Choose VectorDrawable so the size is flexible.
It' integrated into Android Studio as plugin.
If you have an SVG image, you can use this to convert it to VectorDrawable too.
Once you have a VectorDrawable, changing its colour and rotation is easy like others have mentioned.
I'm trying to make a circular progress bar on android and it seems pretty straightforward task , but I'm struggling with rounding the edges of the progress and secondary progress.
Is there a way to do that without making a custom view ? Using a corners radius ? or nine patch drawable ?
For this view (see attachement) I'm using a simple xml file
<item android:id="#android:id/progress">
<shape
android:useLevel="true"
android:innerRadius="#dimen/sixty_dp"
android:shape="ring"
android:thickness="#dimen/seven_dp">
<solid android:color="#477C5B"/>
<stroke android:width="1dip"
android:color="#FFFF"/>
</shape>
</item>
Just create class called MyProgress in your package .. and paste the following code..
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
public class MyProgress extends View {
private Paint mPrimaryPaint;
private Paint mSecondaryPaint;
private RectF mRectF;
private TextPaint mTextPaint;
private Paint mBackgroundPaint;
private boolean mDrawText = false;
private int mSecondaryProgressColor;
private int mPrimaryProgressColor;
private int mBackgroundColor;
private int mStrokeWidth;
private int mProgress;
private int mSecodaryProgress;
private int mTextColor;
private int mPrimaryCapSize;
private int mSecondaryCapSize;
private boolean mIsPrimaryCapVisible;
private boolean mIsSecondaryCapVisible;
private int x;
private int y;
private int mWidth = 0, mHeight = 0;
public MyProgress(Context context) {
super(context);
init(context, null);
}
public MyProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MyProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
void init(Context context, AttributeSet attrs) {
TypedArray a;
if (attrs != null) {
a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyProgress,
0, 0);
} else {
throw new IllegalArgumentException("Must have to pass the attributes");
}
try {
mDrawText = a.getBoolean(R.styleable.MyProgress_showProgressText, false);
mBackgroundColor = a.getColor(R.styleable.MyProgress_backgroundColor, android.R.color.darker_gray);
mPrimaryProgressColor = a.getColor(R.styleable.MyProgress_progressColor, android.R.color.darker_gray);
mSecondaryProgressColor = a.getColor(R.styleable.MyProgress_secondaryProgressColor, android.R.color.black);
mProgress = a.getInt(R.styleable.MyProgress_progress, 0);
mSecodaryProgress = a.getInt(R.styleable.MyProgress_secondaryProgress, 0);
mStrokeWidth = a.getDimensionPixelSize(R.styleable.MyProgress_strokeWidth, 20);
mTextColor = a.getColor(R.styleable.MyProgress_textColor, android.R.color.black);
mPrimaryCapSize = a.getInt(R.styleable.MyProgress_primaryCapSize, 20);
mSecondaryCapSize = a.getInt(R.styleable.MyProgress_secodaryCapSize, 20);
mIsPrimaryCapVisible = a.getBoolean(R.styleable.MyProgress_primaryCapVisibility, true);
mIsSecondaryCapVisible = a.getBoolean(R.styleable.MyProgress_secodaryCapVisibility, true);
} finally {
a.recycle();
}
mBackgroundPaint = new Paint();
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setStyle(Paint.Style.STROKE);
mBackgroundPaint.setStrokeWidth(mStrokeWidth);
mBackgroundPaint.setColor(mBackgroundColor);
mPrimaryPaint = new Paint();
mPrimaryPaint.setAntiAlias(true);
mPrimaryPaint.setStyle(Paint.Style.STROKE);
mPrimaryPaint.setStrokeWidth(mStrokeWidth);
mPrimaryPaint.setColor(mPrimaryProgressColor);
mSecondaryPaint = new Paint();
mSecondaryPaint.setAntiAlias(true);
mSecondaryPaint.setStyle(Paint.Style.STROKE);
mSecondaryPaint.setStrokeWidth(mStrokeWidth - 2);
mSecondaryPaint.setColor(mSecondaryProgressColor);
mTextPaint = new TextPaint();
mTextPaint.setColor(mTextColor);
mRectF = new RectF();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom());
mTextPaint.setTextSize(w / 5);
x = (w / 2) - ((int) (mTextPaint.measureText(mProgress + "%") / 2));
y = (int) ((h / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
mWidth = w;
mHeight = h;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPrimaryPaint.setStyle(Paint.Style.STROKE);
mSecondaryPaint.setStyle(Paint.Style.STROKE);
// for drawing a full progress .. The background circle
canvas.drawArc(mRectF, 0, 360, false, mBackgroundPaint);
// for drawing a secondary progress circle
int secondarySwipeangle = (mSecodaryProgress * 360) / 100;
canvas.drawArc(mRectF, 270, secondarySwipeangle, false, mSecondaryPaint);
// for drawing a main progress circle
int primarySwipeangle = (mProgress * 360) / 100;
canvas.drawArc(mRectF, 270, primarySwipeangle, false, mPrimaryPaint);
// for cap of secondary progress
int r = (getHeight() - getPaddingLeft() * 2) / 2; // Calculated from canvas width
double trad = (secondarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
int x = (int) (r * Math.cos(trad));
int y = (int) (r * Math.sin(trad));
mSecondaryPaint.setStyle(Paint.Style.FILL);
if (mIsSecondaryCapVisible)
canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mSecondaryCapSize, mSecondaryPaint);
// for cap of primary progress
trad = (primarySwipeangle - 90) * (Math.PI / 180d); // = 5.1051
x = (int) (r * Math.cos(trad));
y = (int) (r * Math.sin(trad));
mPrimaryPaint.setStyle(Paint.Style.FILL);
if (mIsPrimaryCapVisible)
canvas.drawCircle(x + (mWidth / 2), y + (mHeight / 2), mPrimaryCapSize, mPrimaryPaint);
if (mDrawText)
canvas.drawText(mProgress + "%", x, y, mTextPaint);
}
public void setDrawText(boolean mDrawText) {
this.mDrawText = mDrawText;
invalidate();
}
public void setBackgroundColor(int mBackgroundColor) {
this.mBackgroundColor = mBackgroundColor;
invalidate();
}
public void setSecondaryProgressColor(int mSecondaryProgressColor) {
this.mSecondaryProgressColor = mSecondaryProgressColor;
invalidate();
}
public void setPrimaryProgressColor(int mPrimaryProgressColor) {
this.mPrimaryProgressColor = mPrimaryProgressColor;
invalidate();
}
public void setStrokeWidth(int mStrokeWidth) {
this.mStrokeWidth = mStrokeWidth;
invalidate();
}
public void setProgress(int mProgress) {
this.mProgress = mProgress;
invalidate();
}
public void setSecondaryProgress(int mSecondaryProgress) {
this.mSecodaryProgress = mSecondaryProgress;
invalidate();
}
public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
invalidate();
}
public void setPrimaryCapSize(int mPrimaryCapSize) {
this.mPrimaryCapSize = mPrimaryCapSize;
invalidate();
}
public void setSecondaryCapSize(int mSecondaryCapSize) {
this.mSecondaryCapSize = mSecondaryCapSize;
invalidate();
}
public boolean isPrimaryCapVisible() {
return mIsPrimaryCapVisible;
}
public void setIsPrimaryCapVisible(boolean mIsPrimaryCapVisible) {
this.mIsPrimaryCapVisible = mIsPrimaryCapVisible;
}
public boolean isSecondaryCapVisible() {
return mIsSecondaryCapVisible;
}
public void setIsSecondaryCapVisible(boolean mIsSecondaryCapVisible) {
this.mIsSecondaryCapVisible = mIsSecondaryCapVisible;
}
public int getSecondaryProgressColor() {
return mSecondaryProgressColor;
}
public int getPrimaryProgressColor() {
return mPrimaryProgressColor;
}
public int getProgress() {
return mProgress;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
public int getSecodaryProgress() {
return mSecodaryProgress;
}
public int getPrimaryCapSize() {
return mPrimaryCapSize;
}
public int getSecondaryCapSize() {
return mSecondaryCapSize;
}
}
and add the following line in res->values->attr.xml under a tag and build it
<declare-styleable name="MyProgress">
<attr name="showProgressText" format="boolean" />
<attr name="progress" format="integer" />
<attr name="secondaryProgress" format="integer" />
<attr name="progressColor" format="color" />
<attr name="secondaryProgressColor" format="color" />
<attr name="backgroundColor" format="color" />
<attr name="primaryCapSize" format="integer" />
<attr name="secodaryCapSize" format="integer" />
<attr name="primaryCapVisibility" format="boolean" />
<attr name="secodaryCapVisibility" format="boolean" />
<attr name="strokeWidth" format="dimension" />
<attr name="textColor" format="color" />
</declare-styleable>
that's it ....
and to use in your layout ..
<Your_Package_Name.MyProgress
android:padding="20dp"
android:id="#+id/timer1"
app:strokeWidth="10dp"
app:progress="30"
app:secondaryProgress="50"
app:backgroundColor="#android:color/black"
app:progressColor="#android:color/holo_blue_bright"
app:secondaryProgressColor="#android:color/holo_blue_dark"
app:primaryCapSize="30"
app:secodaryCapSize="40"
app:primaryCapVisibility="true"
app:secodaryCapVisibility="true"
android:layout_width="200dp"
android:layout_height="200dp" />
You can also change all property progrmatically using setMethods()...
feel free to ask anything ..
best of luck
[Update 23-01-2016]
finally I uploaded code on github.
You can refer it from here
https://github.com/msquare097/MProgressBar
Now You can use this ProgressBar by simply writing following line in your app build.gradle file. You don't have to copy above code.
compile 'com.msquare.widget.mprogressbar:mprogressbar:1.0.0'
I was able to achieve this using a layer list, and adding a dot to each side of the line. The first one will be stuck at the top, while the second one will follow the progress as an inset is added inside a rotate element. You will have to adjust it according to the size of your progressbar layout though. Mine is 250dp x 250dp.
progress_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate android:fromDegrees="270" android:toDegrees="270">
<shape
android:innerRadiusRatio="2.55"
android:shape="ring"
android:thickness="15dp"
android:useLevel="true">
<solid android:color="#color/main_color" />
</shape>
</rotate>
</item>
<item android:bottom="211dp">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="7dp"
android:useLevel="false">
<solid android:color="#color/main_color" />
</shape>
</item>
<item>
<rotate>
<inset android:insetBottom="211dp">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="7dp"
android:useLevel="false">
<solid android:color="#color/main_color" />
</shape>
</inset>
</rotate>
</item>
</layer-list>
I don't have a secondary process here, but you should be able to adjust it for that as well.
A simple and efficient class extending View to draw circular progress, with rounded corners as an option. Progress color, background color, stroke width are also customizable. As seen in my other answer.
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.annotation.FloatRange
class CircularProgressView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val progressPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
}
private val rect = RectF()
private val startAngle = -90f
private val maxAngle = 360f
private val maxProgress = 100
private var diameter = 0f
private var angle = 0f
override fun onDraw(canvas: Canvas) {
drawCircle(maxAngle, canvas, backgroundPaint)
drawCircle(angle, canvas, progressPaint)
}
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
diameter = Math.min(width, height).toFloat()
updateRect()
}
private fun updateRect() {
val strokeWidth = backgroundPaint.strokeWidth
rect.set(strokeWidth, strokeWidth, diameter - strokeWidth, diameter - strokeWidth)
}
private fun drawCircle(angle: Float, canvas: Canvas, paint: Paint) {
canvas.drawArc(rect, startAngle, angle, false, paint)
}
private fun calculateAngle(progress: Float) = maxAngle / maxProgress * progress
fun setProgress(#FloatRange(from = 0.0, to = 100.0) progress: Float) {
angle = calculateAngle(progress)
invalidate()
}
fun setProgressColor(color: Int) {
progressPaint.color = color
invalidate()
}
fun setProgressBackgroundColor(color: Int) {
backgroundPaint.color = color
invalidate()
}
fun setProgressWidth(width: Float) {
progressPaint.strokeWidth = width
backgroundPaint.strokeWidth = width
updateRect()
invalidate()
}
fun setRounded(rounded: Boolean) {
progressPaint.strokeCap = if (rounded) Paint.Cap.ROUND else Paint.Cap.BUTT
invalidate()
}
}
you can add a circle/oval shape on the end & begin side of the ring, just like the below code
a layer-list drawable contain two circle/oval shape drawable and a ring shape drawable
round_progress_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="294px"
android:left="540px"
android:right="48px"
android:top="294px">
<shape
android:innerRadius="6px"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item>
<shape
android:innerRadius="240px"
android:shape="ring"
android:thickness="12px">
<size
android:width="600px"
android:height="600px"/>
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item>
<rotate>
<layer-list>
<item
android:bottom="294px"
android:left="540px"
android:right="48px"
android:top="294px">
<shape
android:innerRadius="6px"
android:shape="oval">
<solid android:color="#FFFFFF"/>
</shape>
</item>
</layer-list>
</rotate>
</item>
</layer-list>
round_progress_animation_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="true">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:propertyName="ImageLevel"
android:repeatCount="infinite"
android:valueFrom="0"
android:valueTo="10000"
android:valueType="intType">
</objectAnimator>
</item>
<item>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="0"
android:propertyName="ImageLevel"
android:valueFrom="0"
android:valueTo="0"
android:valueType="intType">
</objectAnimator>
</item>
</selector>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<ImageView
android:stateListAnimator="#animator/round_progress_animation_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/round_progress_drawable" />
</FrameLayout>
I tried to tackle this for arbitrary size progress bars and if you can ensure the progress bar is a square and you don't use margins, this should work but it doesn't. Android 10 renders it sometimes correctly sometime it doesn't. Especially progress values > 50% tend to break badly. It is not a working solution but might give someone an idea. There are still issues with the gradient fill. The end color doesn't match it. But if you use solid colors, it should work fine. Also the rounded cap causes the bar to show larger value range. My idea was to replace the ring with a shape that is 5dp x 2.5dp rectangle in background color with clipped half a circle. You would need to use different rotations for the start and end:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape
android:innerRadiusRatio="2.4"
android:shape="ring"
android:thickness="5dp"
android:useLevel="true"><!-- this line fixes the issue for lollipop api 21 -->
<gradient
android:angle="0"
android:endColor="#color/yellow"
android:startColor="#color/orange"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
</item>
<item
android:gravity="top">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="2.5dp"
android:useLevel="false">
<solid android:color="#color/orange" />
<size android:height="5dp"/>
</shape>
</item>
<item>
<rotate>
<scale android:scaleGravity="top|center_horizontal"
android:scaleHeight="150%"
android:scaleWidth="150%">
<shape
android:innerRadiusRatio="1000"
android:shape="ring"
android:thickness="2.5dp"
android:useLevel="false">
<solid android:color="#color/yellow" />
</shape>
</scale>
</rotate>
</item>
</layer-list>
How can we create ballon drawable shape as below. where we can change the color of it dynamically.
Here it is XML for triangle and rectangle. save it inside drawable folder.
triangle.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="87%" >
<shape
android:shape="rectangle" >
<stroke android:color="#android:color/transparent" android:width="10dp"/>
<solid
android:color="#000000" />
</shape>
</rotate>
</item>
</layer-list>
rectangle.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<solid android:color="#B2E3FA" />
</shape>
</item>
</layer-list>
and layout for shape you require.
<RelativeLayout
android:id="#+id/rlv1"
android:layout_width="150dp"
android:layout_height="50dp"
android:background="#drawable/rectangle" />
<RelativeLayout
android:id="#+id/rlv2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_below="#+id/rlv1"
android:background="#drawable/triangle"
android:rotation="180" />
set margin according you required.
Source
If you want a border for your layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="#+id/text_message"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="#drawable/bg_rectangle"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:padding="8dp"
android:text="Abc"
/>
<ImageView
android:id="#+id/image_arrow"
android:layout_marginTop="-1.5dp"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_horizontal"
android:background="#drawable/icon_arrow_down"
/>
</LinearLayout>
bg_rectangle
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#eaeaea" />
<stroke
android:width="1dp"
android:color="#f00" />
<corners android:radius="8dp" />
</shape>
icon_arrow_down, or you can create triangle by vector like here
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="45"
android:pivotX="135%"
android:pivotY="15%"
android:toDegrees="45"
>
<shape android:shape="rectangle">
<solid android:color="#eaeaea"/>
<stroke
android:width="1dp"
android:color="#f00" />
</shape>
</rotate>
</item>
</layer-list>
The clean and right way to do this whilst keeping it dynamic is to extend the View class.
Then in the onDraw you would do something like this:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
}
private void drawBackground(Canvas canvas) {
int width = (int) mWidth;
int height = (int) mHeight;
Point a = new Point(0, 0);
Point b = new Point(width, 0);
Point c = new Point(width, height - mPointHeight);//mPointedHeight is the length of the triangle... in this case we have it dynamic and can be changed.
Point d = new Point((width/2)+(mPointedHeight/2), height - mPointHeight);
Point e = new Point((width/2), height);// this is the sharp point of the triangle
Point f = new Point((width/2)-(mPointedHeight/2), height - mPointHeight);
Point g = new Point(0, height - mPointHeight);
Path path = new Path();
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.lineTo(d.x, d.y);
path.lineTo(e.x, e.y);
path.lineTo(f.x, f.y);
path.lineTo(g.x, g.y);
canvas.drawPath(path, mPointedBackgroundPaint);// mPointedBackgroundPaint is whatever color you want as the fill.
}
There you go, no unnecessary layering or code that isn't dynamic or clean. You could also add the text in the box too.
Use a triangle image and a rectangular image and mathematically align them in the above mentioned format. Use color filtering to dynamically change its color.
You can even draw them on a custom view, using vector graphics, using custom colors, and that would be another way of solving this problem.
Create custom view and draw traingle with canvas
package com.example.dickbutt;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
public class TriangleShapeView extends View {
public int colorCode = Color.MAGENTA;
public int getColorCode() {
return colorCode;
}
public void setColorCode(int colorCode) {
this.colorCode = colorCode;
}
public TriangleShapeView(Context context) {
super(context);
}
public TriangleShapeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TriangleShapeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth() / 2;
int h = getHeight() / 2;
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(w, 2 * h);
path.lineTo(2 * w, 0);
path.lineTo(0, 0);
path.close();
Paint p = new Paint();
p.setColor(colorCode);
p.setAntiAlias(true);
canvas.drawPath(path, p);
}
}
Result
Usage
<TextView
android:id="#+id/progress_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#android:color/holo_purple"
android:gravity="center_horizontal"
android:text="200,0000000"
android:textColor="#fff" />
<com.example.dickbutt.TriangleShapeView
android:id="#+id/textView1"
android:layout_width="10dp"
android:layout_height="20dp"
android:layout_below="#+id/progress_value"
android:layout_centerHorizontal="true"
android:background="#drawable/rectangle"
android:gravity="center_horizontal"
android:textSize="10sp" />
Advantages
Change shape according to width and height of view .
Highly customization possible.
Look cleaner
Use Canvas in onDraw method inside custom View class.
Other way is to use Path class.
First you can create one xml inside drawable folder
That xml will be responsible for the border color of rectangle shape
You can create such border shape with below code
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#B2E3FA" />
</shape>
</item>
<item android:left="5dp" android:bottom="5dp" android:top="5dp" >
<shape android:shape="rectangle">
<solid android:color="#D8D8D8" />
</shape>
</item>
</layer-list>
well this will create a required border to rectangle shape, you need to assign background of that rectangle shape with this drawable like this
android:background="#drawable/bg"
where bg is xml file name which has been saved on drawable folder
After that you need to put that triangle exactly below to rectangle object.
I hope you understood my logic
Is there a way that I can specify a triangle shape in an XML file?
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="triangle">
<stroke android:width="1dip" android:color="#FFF" />
<solid android:color="#FFF" />
</shape>
Can we do this with a path shape or something? I just need an equilateral triangle.
Thanks
In this post I describe how to do it. And here is the XML defining triangle:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="87%" >
<shape
android:shape="rectangle" >
<stroke android:color="#color/transparent" android:width="10dp"/>
<solid
android:color="#color/your_color_here" />
</shape>
</rotate>
</item>
</layer-list>
Refer to my post if something is unclear or you need explanation how it is built. It is rotated an cutout rectangle :) it is very smart and well working solution.
EDIT:
to create an arrow pointing like --> use:
...
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="13%"
android:pivotY="-40%" >
...
And to create an arrow pointing like <-- use:
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="87%"
android:pivotY="140%" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="▼"/>
You can get here more options.
You can use vector to make triangle like this
ic_triangle_right.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:pathData="M0,12l0,12 11.5,-5.7c6.3,-3.2 11.5,-6 11.5,-6.3 0,-0.3 -5.2,-3.1 -11.5,-6.3l-11.5,-5.7 0,12z"
android:strokeColor="#00000000"
android:fillColor="#000000"/>
</vector>
Then use it like
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_triangle_right"
/>
For change the color and direction, use android:tint and android:rotation
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="#drawable/ic_triangle_right"
android:rotation="180" // change direction
android:tint="#00f" // change color
/>
Result
For change the shape of vector, you can change the width/height of vector. Example change width to 10dp
<vector
android:width="10dp"
android:height="24dp"
>
...
</vector>
You can use vector drawables.
If your minimum API is lower than 21, Android Studio automatically creates PNG bitmaps for those lower versions at build time (see Vector Asset Studio). If you use the support library, Android even manages "real vectors" down to API 7 (more on that in the update of this post at the bottom).
A red upwards pointing triangle would be:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="100dp"
android:width="100dp"
android:viewportHeight="100"
android:viewportWidth="100" >
<group
android:name="triableGroup">
<path
android:name="triangle"
android:fillColor="#FF0000"
android:pathData="m 50,0 l 50,100 -100,0 z" />
</group>
</vector>
Add it to your layout and remember to set clipChildren="false" if you rotate the triangle.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<ImageView
android:layout_width="130dp"
android:layout_height="100dp"
android:rotation="0"
android:layout_centerInParent="true"
android:background="#drawable/triangle"/>
</RelativeLayout>
Change the size (width/height) of the triangle by setting the Views layout_width/layout_height attributes. This way you can also get an eqilateral triagle if you do the math correct.
UPDATE 25.11.2017
If you use the support library you can use real vectors (instead if bitmap creation) as far back as API 7. Simply add:
vectorDrawables.useSupportLibrary = true
do your defaultConfig in your module's build.gradle.
Then set the (vector xml) drawable like this:
<ImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:srcCompat="#drawable/triangle" />
Everything is documented very nicely on the Vector Asset Studio page.
Ever since this feature I've been working entirely without bitmaps in terms of icons. This also reduces APK size quite a bit.
The solution of Jacek Milewski works for me and, based on his solution, if you need and inversed triangle you can use this:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="135%"
android:pivotY="15%">
<shape android:shape="rectangle">
<solid android:color="#color/aquamarine" />
</shape>
</rotate>
</item>
</layer-list>
I would definetely go for implementing a View in this case:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
public class TriangleShapeView extends View {
public TriangleShapeView(Context context) {
super(context);
}
public TriangleShapeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TriangleShapeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth() / 2;
Path path = new Path();
path.moveTo( w, 0);
path.lineTo( 2 * w , 0);
path.lineTo( 2 * w , w);
path.lineTo( w , 0);
path.close();
Paint p = new Paint();
p.setColor( Color.RED );
canvas.drawPath(path, p);
}
}
Make use of it in your layouts as follows:
<TriangleShapeView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff487fff">
</TriangleShapeView>
Using this implementation will give you the following result:
See answer here:
Custom arrows without image: Android
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="24dp"
android:viewportWidth="32.0"
android:viewportHeight="24.0">
<path android:fillColor="#e4e4e8"
android:pathData="M0 0 h32 l-16 24 Z"/>
</vector>
Using vector drawable:
<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:pathData="M0,0 L24,0 L0,24 z"
android:strokeColor="#color/color"
android:fillColor="#color/color"/>
</vector>
May I help you without using XML ?
Simply,
Custom Layout ( Slice ) :
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
public class Slice extends View {
Paint mPaint;
Path mPath;
public enum Direction {
NORTH, SOUTH, EAST, WEST
}
public Slice(Context context) {
super(context);
create();
}
public Slice(Context context, AttributeSet attrs) {
super(context, attrs);
create();
}
public void setColor(int color) {
mPaint.setColor(color);
invalidate();
}
private void create() {
mPaint = new Paint();
mPaint.setStyle(Style.FILL);
mPaint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
mPath = calculate(Direction.SOUTH);
canvas.drawPath(mPath, mPaint);
}
private Path calculate(Direction direction) {
Point p1 = new Point();
p1.x = 0;
p1.y = 0;
Point p2 = null, p3 = null;
int width = getWidth();
if (direction == Direction.NORTH) {
p2 = new Point(p1.x + width, p1.y);
p3 = new Point(p1.x + (width / 2), p1.y - width);
} else if (direction == Direction.SOUTH) {
p2 = new Point(p1.x + width, p1.y);
p3 = new Point(p1.x + (width / 2), p1.y + width);
} else if (direction == Direction.EAST) {
p2 = new Point(p1.x, p1.y + width);
p3 = new Point(p1.x - width, p1.y + (width / 2));
} else if (direction == Direction.WEST) {
p2 = new Point(p1.x, p1.y + width);
p3 = new Point(p1.x + width, p1.y + (width / 2));
}
Path path = new Path();
path.moveTo(p1.x, p1.y);
path.lineTo(p2.x, p2.y);
path.lineTo(p3.x, p3.y);
return path;
}
}
Your Activity ( Example ) :
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
public class Layout extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Slice mySlice = new Slice(getApplicationContext());
mySlice.setBackgroundColor(Color.WHITE);
setContentView(mySlice, new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
}
}
Working Example :
Another absolutely simple Calculate function you may interested in ..
private Path Calculate(Point A, Point B, Point C) {
Path Pencil = new Path();
Pencil.moveTo(A.x, A.y);
Pencil.lineTo(B.x, B.y);
Pencil.lineTo(C.x, C.y);
return Pencil;
}
You can add following triangle in background using following xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="100dp"
android:width="100dp"
android:viewportHeight="100"
android:viewportWidth="100" >
<group
android:name="triableGroup">
<path
android:name="triangle"
android:fillColor="#848af8"
android:pathData="M 0,20 L 0,0 L 100,0 L 100,20 L 54,55 l -1,0.6 l -1,0.4 l -1,0.2 l -1,0 l -1,-0 l -1,-0.2 l -1,-0.4 l -1,-0.6 L 46,55 L 0,20 -100,-100 Z" />
</group>
</vector>
The whole logic to customize xml design is in pathData. Consider top-left as (0,0) and design the layout as per your requirement.
Check this answer.
For those who want a right triangle arrow, here you go:
STEP 1: Create a drawable XML file, copy and paste the following XML content into your drawable XML. (Please be informed that you can use any name for your drawable XML file. For my case, I name it "v_right_arrow")
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<rotate
android:fromDegrees="45"
android:toDegrees="-45"
android:pivotX="15%"
android:pivotY="-36%" >
<shape
android:shape="rectangle" >
<stroke android:color="#android:color/transparent" android:width="1dp"/>
<solid
android:color="#000000" />
</shape>
</rotate>
</item>
</layer-list>
STEP 2: In your layout's XML, create a View and bind its background to the drawable XML that you have just created in STEP 1. For my case, I bind v_right_arrow to my View's background property.
<View
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#drawable/v_right_arrow">
</View>
Sample output:
Hope this helps, good luck!
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="87%" >
<shape
android:shape="rectangle" >
<stroke android:color="#android:color/transparent" android:width="0dp"/>
<solid
android:color="#fff" />
</shape>
</rotate>
</item>
</layer-list>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="45"
android:pivotX="135%"
android:pivotY="1%"
android:toDegrees="45">
<shape android:shape="rectangle">
<stroke
android:width="-60dp"
android:color="#android:color/transparent" />
<solid android:color="#color/orange" />
</shape>
</rotate>
</item>
</layer-list>
I try to create triangle image like back button of android navigation bar but I haven't found any solution.
Now, I found the solution with myself and would like to share it.
use xml below
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="20dp"
android:left="480dp"
android:right="60dp"
android:top="20dp">
<shape>
<size android:width="60dp" />
<corners android:radius="80dp"/>
<solid android:color="#AAA" />
</shape>
</item>
<item
android:bottom="480dp"
android:right="70dp"
android:top="20dp">
<rotate
android:fromDegrees="-28"
android:pivotX="96%"
android:pivotY="50%"
android:toDegrees="-20">
<shape>
<corners android:radius="80dp"/>
<size android:height="60dp" />
<solid android:color="#AAA" />
</shape>
</rotate>
</item>
<item
android:bottom="20dp"
android:right="70dp"
android:top="480dp">
<rotate
android:fromDegrees="28"
android:pivotX="96%"
android:pivotY="50%"
android:toDegrees="-20">
<shape>
<corners android:radius="80dp"/>
<size android:height="60dp" />
<solid android:color="#AAA" />
</shape>
</rotate>
</item>
I have never done this, but from what I understand you can use the PathShape class:
http://developer.android.com/reference/android/graphics/drawable/shapes/PathShape.html
I my be late to party and I came across same problem and Google pointed me to this StackOverflow thread as first result.
I tried using xml way to add triangle and find out a problem that the triangle
shape via xml approach is taking more space than it appears.
See screen shot with layout bounds on
So ended up making this custom view class which can draws Triangle of any of following types:-
up pointing
down pointing
left pointing &
right pointing
asas
package com.hiteshsahu.materialupvotewidget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
public class TriangleShapeView extends View {
private int colorCode = Color.DKGRAY;
public int getColorCode() {
return colorCode;
}
public void setColorCode(int colorCode) {
this.colorCode = colorCode;
}
public TriangleShapeView(Context context) {
super(context);
if (isInEditMode())
return;
}
public TriangleShapeView(Context context, AttributeSet attrs) {
super(context, attrs);
if (isInEditMode())
return;
}
public TriangleShapeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode())
return;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth() / 2;
int h = getHeight() / 2;
//Choose what type of triangle you want here
Path path = getLeftTriangle(w, h);
path.close();
Paint p = new Paint();
p.setColor(colorCode);
p.setAntiAlias(true);
canvas.drawPath(path, p);
}
#NonNull
/**
* Return Path for down facing triangle
*/
private Path getInvertedTriangle(int w, int h) {
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(w, 2 * h);
path.lineTo(2 * w, 0);
path.lineTo(0, 0);
return path;
}
#NonNull
/**
* Return Path for Up facing triangle
*/
private Path getUpTriangle(int w, int h) {
Path path = new Path();
path.moveTo(0, 2 * h);
path.lineTo(w, 0);
path.lineTo(2 * w, 2 * h);
path.lineTo(0, 2 * h);
return path;
}
#NonNull
/**
* Return Path for Right pointing triangle
*/
private Path getRightTriangle(int w, int h) {
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(2 * w, h);
path.lineTo(0, 2 * h);
path.lineTo(0, 0);
return path;
}
#NonNull
/**
* Return Path for Left pointing triangle
*/
private Path getLeftTriangle(int w, int h) {
Path path = new Path();
path.moveTo(2 * w, 0);
path.lineTo(0, h);
path.lineTo(2 * w, 2 * h);
path.lineTo(2 * w, 0);
return path;
}
}
You can Simply use it in xml layout like this
<com.hiteshsahu.materialupvote.TriangleShapeView
android:layout_width="50dp"
android:layout_height="50dp"></com.hiteshsahu.materialupvote.TriangleShapeView>
I know OP want solutions in xml solution but as I pointed out problem with xml approach . I Hope it might help somebody.
Using the solution of Jacek Milewski I made an oriented down angle with a transparent background.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="135"
android:pivotX="65%"
android:pivotY="20%"
android:toDegrees="135"
>
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#color/blue"
/>
<solid android:color="#color/transparent" />
</shape>
</rotate>
</item>
</layer-list>
You can change android:pivotX and android:pivotY to shift the angle.
Usage:
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:src="#drawable/ic_angle_down"
/>
Parameters depend on the size of the image. For instance, if ImageView has size 100dp*80dp, you should use these constants:
<rotate
android:fromDegrees="135"
android:pivotX="64.5%"
android:pivotY="19%"
android:toDegrees="135"
>
I provide this customView below if you don't want to hack xml.
Please have a try.
/**
* TriangleView
*
* #author Veer
* #date 2020-09-03
*/
class TriangleView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var triangleColor: Int = 0
private var direction = Direction.Bottom
private val paint by lazy {
Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = triangleColor
}
}
init {
initStyle(context, attrs, defStyleAttr)
}
private fun initStyle(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.TriangleView, defStyleAttr, 0)
with(ta) {
triangleColor =
getColor(R.styleable.TriangleView_triangle_background, Color.parseColor("#000000"))
val directionValue =
getInt(R.styleable.TriangleView_triangle_direction, Direction.Bottom.value)
direction = when (directionValue) {
Direction.Top.value -> Direction.Top
Direction.Bottom.value -> Direction.Bottom
Direction.Left.value -> Direction.Left
Direction.Right.value -> Direction.Right
else -> Direction.Bottom
}
recycle()
}
}
override fun onDraw(canvas: Canvas) {
calculatePath(direction).let {
canvas.drawPath(it, paint)
}
}
private fun calculatePath(direction: Direction): Path {
var p1: Point? = null
var p2: Point? = null
var p3: Point? = null
val width = width
val height = height
when (direction) {
Direction.Top -> {
p1 = Point(0, height)
p2 = Point(width / 2, 0)
p3 = Point(width, height)
}
Direction.Bottom -> {
p1 = Point(0, 0)
p2 = Point(width / 2, height)
p3 = Point(width, 0)
}
Direction.Left -> {
p1 = Point(width, 0)
p2 = Point(0, height / 2)
p3 = Point(width, height)
}
Direction.Right -> {
p1 = Point(0, 0)
p2 = Point(width, height / 2)
p3 = Point(0, height)
}
}
val path = Path()
path.moveTo(p1.x.toFloat(), p1.y.toFloat())
path.lineTo(p2.x.toFloat(), p2.y.toFloat())
path.lineTo(p3.x.toFloat(), p3.y.toFloat())
return path
}
private enum class Direction(val value: Int) {
Top(0),
Bottom(1),
Left(2),
Right(3)
}
}
<declare-styleable name="TriangleView">
<attr name="triangle_direction" format="enum">
<enum name="top" value="0" />
<enum name="bottom" value="1" />
<enum name="left" value="2" />
<enum name="right" value="3" />
</attr>
<attr name="triangle_background" format="reference|color" />
</declare-styleable>
<LinearLayout
android:id="#+id/ly_fill_color_shape"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:background="#drawable/shape_triangle"
android:gravity="center"
android:orientation="horizontal" >
</LinearLayout>
<item>
<rotate
android:fromDegrees="45"
android:pivotX="-15%"
android:pivotY="77%"
android:toDegrees="45" >
<shape android:shape="rectangle" >
<stroke
android:width="2dp"
android:color="#color/black_color" />
<solid android:color="#color/white" />
<padding android:left="1dp" />
</shape>
</rotate>
</item>
<item android:top="200dp">
<shape android:shape="line" >
<stroke
android:width="1dp"
android:color="#color/black_color" />
<solid android:color="#color/white" />
</shape>
</item>
Google provides a Equilateral triangle here.
Choose VectorDrawable so the size is flexible.
It' integrated into Android Studio as plugin.
If you have an SVG image, you can use this to convert it to VectorDrawable too.
Once you have a VectorDrawable, changing its colour and rotation is easy like others have mentioned.