I'd like to create a rectangle shape with two solid colors (horizontally) to achieve something like this:
I heard about layer-list, i though i could use it to contains two rectangle with a different color but it seems that it only lays shapes vertically.
Is there a way to achieve this using lalyer-list or should i use something totally different? I'd like to keep it simple with ability to change the shape colors at runtime.
Thanks.
this will surely draw the shape as per your Requirement :
Adjust size of <item> as you need !
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:left="50dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#0000FF" />
</shape>
</item>
<item android:right="50dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#ff0000" />
</shape>
</item>
</layer-list>
You can create custom drawable for this. Just extend Drawable class.
Here is a sample code which draws a rectangle like you wanted, you can provide any number of colors.
public class ColorBarDrawable extends Drawable {
private int[] themeColors;
public ColorBarDrawable(int[] themeColors) {
this.themeColors = themeColors;
}
#Override
public void draw(Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
// draw background gradient
Paint backgroundPaint = new Paint();
int barWidth = width / themeColors.length;
int barWidthRemainder = width % themeColors.length;
for (int i = 0; i < themeColors.length; i++) {
backgroundPaint.setColor(themeColors[i]);
canvas.drawRect(i * barWidth, 0, (i + 1) * barWidth, height, backgroundPaint);
}
// draw remainder, if exists
if (barWidthRemainder > 0) {
canvas.drawRect(themeColors.length * barWidth, 0, themeColors.length * barWidth + barWidthRemainder, height, backgroundPaint);
}
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
This will give you two colors half and half vertically. Put this code in a drawable resource.
<item
android:top="320dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/red" />
</shape>
</item>
<item android:bottom="320dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/yellow" />
</shape>
</item>
Related
I am trying to have a layout background drawable, which will be only gradient underline with 1-2 dp height and rest is transparent, so the upper part will have the parent's background.
Here is what I have.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android>
<!-- underline color -->
<item>
<shape>
<gradient
android:startColor="#color/colorPrimaryDark"
android:endColor="#FFFFFFFF"
android:centerY="0.5"
android:angle="0"/>
</shape>
</item>
<!-- main color -->
<item android:bottom="2.5dp">
<shape android:shape="rectangle">
<solid android:color="#color/white" />
<padding
android:top="4dp"
android:bottom="4dp" />
</shape>
</item>
If I change the solid color in "main color" to transparent, whole background will be using "underline color" settings.
The technique you use to create a line on the bottom of the view works if the color of the layer overlaying the gradient layer is opaque. What you are trying to do is to apply a transparent layer that replaces (erases) the underlying gradient. That is not how it works: A transparent overlay leaves the underlying color, here a gradient, untouched.
Here is an alternate layer-list drawable that you can use for API 23+:
underline_drawable.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="bottom">
<shape>
<size android:height="2dp" />
<gradient
android:angle="0"
android:centerY="0.5"
android:endColor="#FFFFFFFF"
android:startColor="#color/colorPrimaryDark" />
</shape>
</item>
</layer-list>
Here is what it looks like:
Prior to API 23, you can use the following custom drawable, but it must be set in code.
GradientUnderline.java
public class GradientUnderline extends Drawable {
private Shader mShader;
private final Paint mPaint;
private int mHeight = -1;
private int mStartColor = Color.BLACK;
private int mEndColor = Color.WHITE;
private int mLastWidth;
public GradientUnderline() {
mPaint = new Paint();
}
public GradientUnderline(int lineHeight, int startColor, int endColor) {
mPaint = new Paint();
mHeight = lineHeight;
mStartColor = startColor;
mEndColor = endColor;
}
#Override
public void draw(#NonNull Canvas canvas) {
if (mShader == null || getBounds().width() != mLastWidth) {
mLastWidth = getBounds().width();
mShader = new LinearGradient(0, 0, getBounds().width(), mHeight, mStartColor,
mEndColor, Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
}
canvas.drawRect(0, getBounds().height() - mHeight, getBounds().width(),
getBounds().height(), mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
I missed the availability of android:gravity initially because it is not mentioned on the "Drawable Resources" page. It is mentioned, however, in the LayerDrawable documentation.
Why problem occurs: Shape at first item will draw the gradient in entire region. After setting colour to second item will hide the top item region except ay 2.5dp at bottom. So whenever you set transparent colour to second item it automatically show the top level item that is gradient region..
Here i suggest the way to use but you can set to fixed height in view.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="47dp">
<shape>
<gradient
android:startColor="#color/colorPrimaryDark"
android:endColor="#FFFFFFFF"
android:centerY="0.5"
android:angle="0"/>
</shape>
</item>
</layer-list>
View.xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#drawable/bottom_line">
</RelativeLayout>
Change size according to your needs..!
OUTPUT
I want to get a CardView with the top-right corner like you can see in the image below, but I have no idea how to do so. It is like a folded paper (with no animation). I don't know if I should make a custom background drawable or how to manage the corner radius to get the desired result. Any help will be greatly appreciated, thanks
Also you can create such a drawable programmatically like this:
public static final class FoldCornerCard extends Shape {
private final float foldPart;
private final Path cardPath = new Path();
private final Path foldPath = new Path();
private final Paint foldPaint;
public FoldCornerCard(int foldColor, float foldPart) {
if (foldPart <= 0 || foldPart >= 1) {
throw new IllegalArgumentException("Fold part must be in (0,1)");
}
this.foldPart = foldPart;
this.foldPaint = new Paint();
foldPaint.setAntiAlias(true);
foldPaint.setColor(foldColor);
}
#Override
protected void onResize(float width, float height) {
super.onResize(width, height);
this.cardPath.reset();
final float leftFold = width - width * foldPart;
final float bottomFold = height * foldPart;
cardPath.lineTo(leftFold, 0);
cardPath.lineTo(width, bottomFold);
cardPath.lineTo(width, height);
cardPath.lineTo(0, height);
cardPath.close();
foldPath.reset();
foldPath.moveTo(leftFold, 0);
foldPath.lineTo(leftFold, bottomFold);
foldPath.lineTo(width, bottomFold);
foldPath.close();
}
#Override
public void draw(Canvas canvas, Paint paint) {
canvas.drawPath(cardPath, paint);
canvas.drawPath(foldPath, foldPaint);
}
}
And usage example:
final ShapeDrawable shapeDrawable = new ShapeDrawable(
new FoldCornerCard(Color.GREEN, 0.1f));
shapeDrawable.getPaint().setColor(Color.WHITE);
shapeDrawable.setIntrinsicHeight(-1);
shapeDrawable.setIntrinsicWidth(-1);
You just need to modify my snippet a bit to add round corners.
Look at here https://developer.android.com/studio/write/draw9patch.html
I think this is righ way to use custom layout. You could draw it on xml, or use 9-patch png.
Also you can create own class MyCardView and extends from CardView, then override method onDraw and draw CardView like you want, but it is not good idea.
I would reccomend you use 9-patch image
You can achieve this using your xml:
Lets Assume our xml shape is called shape.xml
In shape.xml(Which you have to create in your drawable folder..drawable/shape.xml)..
Create your layer list element with its square shape as the background of your xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--Paper-Back-->
<item
android:bottom="30dp"
android:left="18dp"
android:right="1dp"
android:top="0dp">
<shape android:shape="rectangle">
<solid android:color="#color/paperBack"/>
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="78dp"/>
</shape>
</item>
<!--Paper-Back End-->
<!--Fold-->
<item
android:bottom="650dp"
android:top="0dp"
android:left="300dp"
android:right="1dp">
<shape android:shape="rectangle">
<solid android:color="#color/paperFold"/>
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="100dp"
/>
</shape>
</item>
<!--Fold End-->
</layer-list>
Then in your colours.xml resource:
Add your colours:
color.xml
<color name="PaperBack">#A6F5F5F5</color>
<color name="paperFold">#A6DDDDDD</color>
In order to achieve the best results:The opacity of your colours as well as the type of colour combination for your paper background and fold colours will have to be taken into great consideration.
Now to apply the paper fold shape..in your main xml, use the shape.xml as the background in your main.xml.
using shape.xml as the background
android:background="#drawable/shape.xml"
main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/shape.xml"
android:orientation="vertical"
android:weightSum="4">
.....................
I created an Android gradient drawable where the top and bottom are black and the center is transparent:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:startColor="#android:color/black"
android:centerColor="#android:color/transparent"
android:endColor="#android:color/black"
android:angle="90"/>
</shape>
The rendered gradient looks like this:
As you can see, the black parts spread to most of the screen. I want the black to show only on a small portion of the top and bottom. Is there a way I can make the transparent center larger, or make the top and bottom black stripes smaller?
I tried playing around with some of the other XML attributes mentioned in the linked GradientDrawable documentation, yet none of them seem to make and difference.
For an XML only solution, you can create a layer-list with two separate gradient objects.
The following code creates two overlapping gradient objects and uses centerY with centerColor to offset the black section. Here, the centerY attributes are set to 0.9 and 0.1, so the black color is restricted to the top and bottom 10% of the view height.
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="90"
android:centerColor="#android:color/transparent"
android:centerY="0.9"
android:endColor="#android:color/black"
android:startColor="#android:color/transparent" />
</shape>
</item>
<item>
<shape>
<gradient
android:angle="90"
android:centerColor="#android:color/transparent"
android:centerY="0.1"
android:endColor="#android:color/transparent"
android:startColor="#android:color/black" />
</shape>
</item>
</layer-list>
For API level 23 or higher, the following solution will also work, using android:height. This solution can work even if you don't know the total height of your view, as long as you know how large you want the gradient to be.
This code creates two separate gradients, each with a height of 60sp, and then uses android:gravity to float the gradients to the top and bottom of the view.
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:height="60sp"
android:gravity="top">
<shape android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="#android:color/black"
android:startColor="#android:color/transparent" />
</shape>
</item>
<item
android:height="65sp"
android:gravity="bottom">
<shape android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="#android:color/transparent"
android:startColor="#android:color/black" />
</shape>
</item>
</layer-list>
Thank you #Luksprog for the code help, and #thenaoh for the start of the idea.
The above solutions work and it is nice that they are pure XML. If your gradient is showing with stripes, you may want to try a programmatic solution, like shown in #lelloman's answer, to create a smoother gradient.
Here is how it could be done with a custom Drawable. You can tune the LinearGradient as you want, and then set it as the view's background with view.setBackground(new CustomDrawable());.
public class CustomDrawable extends Drawable {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int[] colors;
private float[] positions;
public CustomDrawable() {
paint.setStyle(Paint.Style.FILL);
this.colors = new int[]{0xff000000, 0xffaaaaaa, 0xffffffff, 0xffaaaaaa, 0xff000000};
this.positions = new float[]{.0f, .2f, .5f, .8f, 1.f};
}
#Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
LinearGradient linearGradient = new LinearGradient(left, top,left, bottom, colors, positions, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
}
#Override
public void draw(#NonNull Canvas canvas) {
canvas.drawRect(getBounds(), paint);
}
#Override
public void setAlpha(#IntRange(from = 0, to = 255) int alpha) {
paint.setAlpha(alpha);
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSPARENT;
}
}
There is a solution, assuming that you know in advance the height of your view (let's say here 60dp):
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="40dp">
<shape android:shape="rectangle">
<gradient
android:type="linear"
android:angle="90"
android:startColor="#FFFFFF"
android:endColor="#000000"/>
</shape>
</item>
<item
android:top="20dp"
android:bottom="20dp"
android:gravity="center_vertical">
<shape android:shape="rectangle">
<solid android:color="#FFFFFF"/>
</shape>
</item>
<item
android:top="40dp"
android:gravity="bottom">
<shape android:shape="rectangle">
<gradient
android:type="linear"
android:angle="90"
android:startColor="#000000"
android:endColor="#FFFFFF"/>
</shape>
</item>
</layer-list>
But if you don't know the height in advance, another solution would be to make your own custom view, like this:
public class MyView extends ImageView
{
private Paint paint = null;
public MyView(Context context, AttributeSet attrs)
{
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
paint.setShader(getLinearGradient(0, getHeight()));
canvas.drawPaint(paint);
}
private LinearGradient getLinearGradient(float y0, float y1)
{
// colors :
int[] listeColors = new int[3];
listeColors[0] = 0xFF000000;
listeColors[1] = 0xFFFFFFFF;
listeColors[2] = 0xFFFFFFFF;
// positions :
float[] listPositions = new float[3];
listPositions[0] = 0;
listPositions[1] = 0.25F;
listPositions[2] = 1;
// gradient :
return new LinearGradient(0, y0, 0, y0 + (y1 - y0) / 2, listeColors, listPositions, Shader.TileMode.MIRROR);
}
}
Hope it helps.
I would like to scale a rectangle drawable downwards, then rotate it so once it is clipped by the view it resembles a trapezoid with the left side slanted:
The rotation is working fine:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<rotate
android:fromDegrees="-19.5"
android:toDegrees="-19.5"
android:pivotX="0%"
android:pivotY="0%"
>
<shape
android:shape="rectangle" >
<solid
android:color="#android:color/black" />
</shape>
</rotate>
</item>
</layer-list>
However to prevent a big gap where the rectangle has rotated away from the bottom of the view I want to scale vertically by 200% before the rotation happens. I was hoping that I could do something like this:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<scale
android:scaleWidth="100%"
android:scaleHeight="200%"
android:scaleGravity="top"
>
<rotate
android:fromDegrees="-19.5"
android:toDegrees="-19.5"
android:pivotX="0%"
android:pivotY="0%"
>
<shape
android:shape="rectangle" >
<solid
android:color="#android:color/black" />
</shape>
</rotate>
</scale>
</item>
</layer-list>
but this just causes the rectangle to disappear. Does anyone know how best to achieve this?
No really a true answer but I solution that I am using now is to create a custom Drawable that draws the shape:
public void setColor(int color) {
_color = color;
}
#Override
public void draw(Canvas canvas) {
int width = this.getBounds().width();
int height = this.getBounds().height();
double angle = 19.5 * (Math.PI / 180.0);
double offsetX = height * Math.tan(angle);
Path path = new Path();
Paint paint = new Paint();
path.moveTo(0, 0);
path.lineTo(width, 0);
path.lineTo(width, height);
path.lineTo((int)offsetX, height);
path.close();
paint.setColor(_color);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
}
This does the job for now, although it is frustrating that I cannot find a way to do this in the xml.
Aim: Stroke only the top and bottom.
What I've tried:
Below is a copy of my XML.
I've tried following the solution in This Stack Overflow Answer.
But my problem is that the doesn't let me choose the options of cutting off the left and right by 1dp as per the solution.
Any ideas?
Code:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape >
<gradient
android:startColor="#color/secondaryButtonStartColorSelected"
android:endColor="#color/secondaryButtonEndColorSelected"
android:angle="270" />
<stroke
android:width="#dimen/secondary_button_border_size"
android:color="#color/secondaryButtonBorderColorSelected" />
</shape>
</item>
<item android:state_focused="true" >
<shape>
<gradient
android:startColor="#color/secondaryButtonStartColorSelected"
android:endColor="#color/secondaryButtonEndColorSelected"
android:angle="270" />
<stroke
android:width="#dimen/secondary_button_border_size"
android:color="#color/secondaryButtonBorderColorSelected"/>
</shape>
</item>
</selector>
You could create a custom Drawable to handle this for you, but you'd have to set it in code vs XML. Here's a quick and dirty version:
public class HorizontalStrokeDrawable extends Drawable {
private Paint mPaint = new Paint();
private int mStrokeWidth;
public HorizontalStrokeDrawable (int strokeColor, int strokeWidth) {
mPaint.setColor(strokeColor);
mStrokeWidth = strokeWidth;
}
#Override
public void draw (Canvas canvas) {
Rect bounds = getBounds();
canvas.drawRect(0, 0, bounds.right, mStrokeWidth, mPaint);
canvas.drawRect(0, bounds.bottom - mStrokeWidth, bounds.right, bounds.bottom, mPaint);
}
#Override
public void setAlpha (int alpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter (ColorFilter cf) {
mPaint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity () {
return PixelFormat.TRANSLUCENT;
}
}
Now you can just set it wherever you need it:
view.setBackgroundDrawable(new HorizontalStrokeDrawable(myColor, myStrokeWidth));