I am trying to build a LayerDrawable in xml where upper layers will occasionally completely obscure lower layers. To make the lower layers smaller, I am using an InsetDrawable to wrap another drawable to make it smaller than the full size of the view. I find unexpectedly, however, that any layers placed on top of the layer containing the inset also has the inset applied to it. I can't find documentation supporting this behavior, and am confused why this would be the case.
In the example below, I make a LayerDrawable with 3 layers. The bottom and top layers contain oval shape drawables that are meant to take up the entire view. The middle layer is a rectangle drawable inside of an InsetDrawable. The code is below:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="oval" >
<solid android:color="#00ff00" />
</shape>
</item>
<item>
<inset
android:insetBottom="4dp"
android:insetLeft="4dp"
android:insetRight="4dp"
android:insetTop="4dp" >
<shape android:shape="rectangle" >
<solid android:color="#ff0000" />
</shape>
</inset>
</item>
<item>
<shape android:shape="oval" >
<solid android:color="#0000ff" />
</shape>
</item>
</layer-list>
Calling setBackgroundDrawable(getResources().getDrawable(drawableId)); in my view produces a green oval that fills the entire view as expected, with a red rectangle inset 4dp as expected, but the blue oval on the top layer is also inset 4dp and drawn completely within the bounds of the red rectangle.
I would expect the blue oval to completely obscure the green oval and most of the red rectangle, but instead it is inset inside the red rectangle. Is there any way to make the blue circle fill the view yet keep it on top?
I also don't see where it is documented, but padding in a LayerDrawable is cumulative. That is, padding at one layer affects the bounds of all higher layers. This is from the source for LayerDrawable:
#Override
protected void onBoundsChange(Rect bounds) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
int padL=0, padT=0, padR=0, padB=0;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
r.mDrawable.setBounds(bounds.left + r.mInsetL + padL,
bounds.top + r.mInsetT + padT,
bounds.right - r.mInsetR - padR,
bounds.bottom - r.mInsetB - padB);
padL += mPaddingL[i];
padR += mPaddingR[i];
padT += mPaddingT[i];
padB += mPaddingB[i];
}
}
(LayerDrawable.getPadding(Rect) follows the same logic.) Since an InsetDrawable uses its insets as padding (as documented), this explains the behavior you're seeing.
I think this is a poor design decision, but you're kind of stuck with it, I'm afraid. I don't think it can be overridden.
Ted's answer is the best answer, but I'll share this workaround that helped me. I was specifically having padding problems with a TextView, so I made a custom TextView instead which ignores the background drawable's padding.
public class HackTextView extends TextView {
public HackTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public HackTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HackTextView(Context context) {
super(context);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public void setBackground(Drawable background) {
super.setBackground(hackDrawable(background));
}
#Override
public void setBackgroundDrawable(Drawable background) {
super.setBackgroundDrawable(hackDrawable(background));
}
private Drawable hackDrawable(Drawable background){
return new LayerDrawable(new Drawable[]{background}){
#Override
public boolean getPadding(Rect padding) {
padding.set(0, 0, 0, 0);
return false;
}
};
}
}
Related
Following this answer I was able to get a divider between the items of a vertical RecyclerView. However, I also wanted to slightly indent the divider lines.
I was able to do it by hard coding in an INDENT value in the RecyclerView.ItemDecoration subclass.
int INDENT = 20;
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft() + INDENT;
int right = parent.getWidth() - parent.getPaddingRight() - INDENT;
// ...
divider.setBounds(left, top, right, bottom);
// ...
}
However, then I would have had to also mess with density independant pixels.
I finally found a solution similar to how it was done with ListView so I am sharing that as an answer below.
Use inset
drawable/my_divider.xml
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="40dp"
android:insetRight="40dp" >
<shape>
<size android:height="1dp"/>
<solid android:color="#color/recyclerview_divider" />
</shape>
</inset>
Using the constructor that takes a resource id as shown in this answer, we can supply the id of our custom divider xml file.
Update:
We can't add drawable in DividerItemDecoration constructor, we need to set drawable after created like in the example below
ItemDecoration dividerItemDecoration = new DividerItemDecoration(
getActivity(),
RecyclerView.VERTICAL
)
dividerItemDecoration.setDrawable(drawable)
recyclerView.addItemDecoration(decorator);
Drawable drawable =
I am using that "hack".
I have read here in stackoverflow.
#Override
public void draw(Canvas canvas) {
for (int i = 0; i < 20; i++) {
super.draw(canvas);
}
}
But my border still smoothie,I wanna put a large and solid border on all my TextView (I already have my component extend a textview).
I have a selector in text color when I click in this text the text color need to change.(It was already working,but I tried to apply another alternative using canvas,in this alternative,I lost this comportment).
This page solve your problem, you can custom the style:
How do I put a border around an Android textview?
You can set a shape drawable (a rectangle) as background for the view.
<TextView android:text="Some text" android:background="#drawable/back"/>
And rectangle drawable back.xml (put into res/drawable folder):
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="#ffffff" />
<stroke android:width="1dip" android:color="#4fa5d5"/>
</shape>
You can use #00000000 for the solid color to have a transparent background. You can also use padding to separate the text from the border. for more information see: http://developer.android.com/guide/topics/resources/drawable-resource.html
Your current solution (the hack) is working fine, you just have to tweak 2 parameters accordingly to get a better "solid" shadow effect.
Parameters
The 1st parameter is the shadow radius of the TextView. This parameter decides how "wide" the blur (shadow) effect will spread behind your letter.
The 2nd parameter is the repeat counter of the for loop that wraps around your TextView's onDraw(...) method. Higher repeat count will get you a more "solid" shadow by trading off the performance.
"Solid" shadow
The rule here is, increment on shadow radius (↑) must always accompany with increment on repeat counter (↑) to achieve the "solid" shadow effect.
Similarly, if you want to gain performance by reducing repeat counter (↓), you have to decrease shadow radius (↓) as well.
Solid shadow TextView
package com.example.solidshadowtext;
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.TextView;
public class SolidShadowTextView extends TextView {
/**
* Shadow radius, higher value increase the blur effect
*/
private static final float SHADOW_RADIUS = 10f;
/**
* Number of times a onDraw(...) call should repeat itself.
* Higher value ends up in more solid shadow (but degrade in performance)
* This value must be >= 1
*/
private static final int REPEAT_COUNTER = 10000;
// Shadow color
private static final int SHADOW_COLOR = 0xff000000;
public SolidShadowTextView(Context context) {
super(context);
init();
}
public SolidShadowTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
#Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < REPEAT_COUNTER; i++) {
super.onDraw(canvas);
}
}
#Override
public void setShadowLayer(float radius, float dx, float dy, int color) {
// Disable public API to set shadow
}
private void init() {
super.setShadowLayer(SHADOW_RADIUS, 0, 0, SHADOW_COLOR);
}
}
Sample
I'm trying to have a circle background for my TextView, so I created a shape style as below.
But I need to have multiple colors with multiple sizes (while the textSize stays constant), so I need to set the width/height in the style.
From my understanding..Layer List puts all the shapes on top of one another? Because I need to call it 12 times at different places, so it seems quite cumbersome to have 12 shape style xmls.
Is there a better way to have all the different shape/size combinations inside one XML?
Shape Style:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<corners android:radius="10dp"/>
<solid android:color="#color/girl_orange"/>
<size
android:width="84dp"
android:height="84dp" />
</shape>
Called in layout xml by:
android:background="#drawable/skills_circle"
Thanks in advance!!
create a custom Drawable, this way you can have milions combinations of size/color:
class CircleDrawable extends Drawable {
...
}
So I followed the advice from pskink and created a CircleDrawable class.
It works quite nicely for my application (although I don't know if it's the right way...), so I thought I'd share it.
public CircleDrawable(Bitmap bitmap, Context context) {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
CircleDrawable.context = context;
drawable = new ShapeDrawable(new OvalShape());
setColor(); // supports multiple color
setSize(); //supports multiple size
}
private void setColor() {
// some algorithm to pick the right color...
if (...)
int color = context.getResources().getColor(R.color.pale_blue);
paint.setColor(color);
}
/*
* algorithm to set size here...
*/
#Override
public void draw(Canvas canvas) {
//draw circle in the middle of the TextView
canvas.drawCircle(textViewSize, textViewSize, circleSize, paint);
}
And in the main code where I need to dynamically draw the circles:
final float scale = getApplicationContext().getResources().getDisplayMetrics().density;
int pixels = (int) (107.0f * scale + 0.5f);
skills.setWidth(pixels);
skills.setHeight(pixels);
skills.setBackground(new CircleDrawable(null, getApplicationContext()));
And I ended up with a bunch of circles with different shapes and colors.
I have a class that implement a view
**DrawView.class**
public class DrawView extends View {
Paint paint = new Paint();
public DrawView(Context context, AttributeSet attrs){
super(context, attrs);
}
and my file.xml
<com.example.sliding.DrawView
android:id="#+id/tv_listRow_item1"
android:tag="tv_listRow_item1_1"
android:layout_height="0dip"
android:layout_width="fill_parent"
android:layout_weight="1"
android:gravity="center"
android:width="100dip"
android:height="30dip"
android:background="#drawable/textview_listrow_border"/>
This view have 30 dip of height. How can i color only 30% of this 30 dip?
Anyone can give me an example?
Thanks for your time and help.
I'm not entirely sure this will work, but you could make a 9 patch that is 30% color and 70% transparent, then define two stretchable areas (one for each) in the appropriate percentages. When 9 patches are stretched, they're supposed to respect the ratios of multiple stretch zones, so I think it would work.
http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
One way is by using a LayerDrawable
But this would only work when the heigt of the view is fixed at 80dp.
Create an xml file in your drawable folder.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#FFFFFFFF" />
</shape>
</item>
<item android:top="10dp">
<shape >
<solid android:color="#FF000000"/>
</shape>
</item>
<item android:top="60dp">
<shape>
<solid android:color="#FFFFFFFF"/>
</shape>
</item>
</layer-list>
And set this as the background to the view.
I NEED A FUNCTION THAT HAVE 2 PARAMETERS. This FIRST INDICATE THE BEGIN OF COLOR. THE SECOND INDICATE THE END. hEIGHT=80dp. THE FIRST PARAMETER FOR EXAMPLE IS 20 THE SECOND IS 30 FOR EXAMPLE. The pixels inside this interval have a color…..
I guess that the easiest way to do it is to override the onDraw(Canvas canvas) function and draw a rectangle like this.
double mStart = -1;
double mEnd = -1;
public void addRectangle( double startInPercent, double endInPercent ) {
if( startInPercent < 0 || endInPercent > 1 || endInPercent > startInPercent )
return;
mStart = startInPercent;
mEnd = endInPercent;
//this will make the view to refresh the UI
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if( mStart >= 0 && mEnd >= 0)
canvas.drawRect(0, getHeight() * mStart, getWidth(), getHeight() * mEnd, mPaint);
}
this code will draw a rectangle as specified in the addRectangle() method. In my implementation I intend the parameters of that function to per % of the Height of the view.
here is the documentation of that drawRect( ... ) call. Changing the parameters you can draw at the top, bottom left and right as you prefer.
In order to get the color you want you have to init mPaint in the view constructor like this:
Paint mPaint = new Paint();
mPaint.setColor( Color.RED );
of course this is the dumbest code possible, you can play around this 2 concept to basically get what you want.
the main class to do this ( and almost everything in the Andorid UI ) are:
Canvas
View
Paint
First I'm new to android.
Okay, here goes.
I'm attempting to override the onDraw of a button, because I want to create a different styled button.. Something ever so slightly different.
Now I can draw the background quite easily, but I can't for the life of me figure out why I have no text on my button.
public class TriButton extends Button {
private Paint m_paint = new Paint();
private int m_color = 0XFF92C84D; //LIKE AN OLIVE GREEN..
public TriButton(Context context) {
super(context);
setBackgroundColor(Color.BLACK);
}
public void onDraw(Canvas iCanvas) {
//draw the button background
m_paint.setColor(m_color);
iCanvas.drawRoundRect(new RectF(0, 0,getWidth(),getHeight()), 30, 30, m_paint);
//draw the text
m_paint.setColor(Color.WHITE);
iCanvas.drawText( "bash is king", 0, 0, m_paint);
}
public static RelativeLayout.LayoutParams GetRelativeParam(int iLeft, int iTop, int iWidth, int iHeight){
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(iHeight, iWidth);
params.leftMargin = iLeft;
params.topMargin = iTop;
return params;
}
}
Here's the code that creates the button.
RelativeLayout relLay = new RelativeLayout(this);
m_button = new TriButton(this);
setContentView(relLay);
relLay.addView(m_button, m_button.GetRelativeParam(0,0,100,500) );
Now everything I've read has me expecting to see text in my olive green button oval button.
The olive green oval shows up, but it doesn't have text in it.. It is a void. A green smudge that laughs at me and reminds me with it's silence, that I am utterly alone :(.
Normally you would do that via xml.
for examle put the following in the layout of your custom class:
android:background="#drawable/shape"
and then something like that which would be shape.xml placed in /drawable.
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="2px"
android:color="#555555"/>
<corners
android:radius="10px"/>
<gradient
android:startColor="#000000"
android:endColor="#ffffff"
android:angle="90"/>
</shape>
This example creates a rounded rectangle with a border and a gradient in it. Let me know if you need further explanation.
See http://developer.android.com/guide/topics/ui/themes.html
Ok, I figured, it's not visible because coordinates are (0,0) which is bottom left of the button, so text is not visible. Try this and it works:
iCanvas.drawText( "bash is king", 0, 15, m_paint);
Olive green is a good choice btw :)
To draw text in your button, get rid of the call to drawText() within the onDraw of your button class. All you need to do is call setText on the instance of the button since Button extends view:
m_button.setText("bash is king");
Also, to make a custom button, you may want to consider just using an Image and assigning an OnClickListener to it.