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
Related
I would like to set a background drawable (or resource) on a TextView, not taking into account its compound drawable width (and paddings).
Getting the width of the compound (left one to be more precise), and its paddings should not be a problem, but the setting of the background on the width of the textview minus the width of the compound drawable (described above).
Should you have any advice on doing that, please let me know.
Here's the needed result:
PS. I thought about having a horizontal LinearLayout with an ImageView and TextView as its children, and having the background set on the textview only, but I am interested in having the same result with less Views (in this case, exactly one), if it is possible.
You could use a LayerDrawable which supports insets, for example:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:left="dimension" android:right="dimension">
<shape android:shape="rectangle">
<solid android:color="color" />
</shape>
</item>
</layer-list>
If you want to change your Drawable dynamically you're better off writing your own Drawable class. The following DividerDrawable for instance draws a line to a given padding onto a white background:
public class DividerDrawable extends Drawable {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mDensity;
private int mPaddingLeft = 0;
public DividerDrawable(Context context) {
mPaint.setColor(Color.BLACK);
mDensity = context.getResources().getDisplayMetrics().density;
}
#Override
public void draw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
canvas.drawColor(Color.WHITE);
canvas.drawRect(mPaddingLeft, height - mDensity, width, height, mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public void setPaddingLeft(int paddingLeft) {
if (mPaddingLeft != paddingLeft) {
mPaddingLeft = paddingLeft;
invalidateSelf();
}
}
}
To set the left padding based on your left CompoundDrawable you could do something like this:
private void setBackground(TextView textView, DividerDrawable background) {
Drawable drawableLeft = textView.getCompoundDrawables()[0];
int paddingLeft = drawableLeft != null ?
textView.getPaddingLeft() + drawableLeft.getIntrinsicWidth() + textView.getCompoundDrawablePadding() :
textView.getPaddingLeft();
background.setPaddingLeft(paddingLeft);
textView.setBackground(background);
}
To make good use of all this call it like so:
DividerDrawable dividerDrawable = new DividerDrawable(this);
TextView textView = (TextView) findViewById(R.id.text);
setBackground(textView, dividerDrawable);
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 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;
}
};
}
}
Here is my unanswered question:
Add new item count to icon on button - Android
Basically I want to display "new" counts on top. I see it as overlaying some view over existing button. How this can be done?
Easiest thing to do is:
Use a RelativeLayout with layout_height and layout_width set to WRAP_CONTENT.
Put one Button into the RelativeLayout with layout_height and layout_width set to WRAP_CONTENT.
Add an ImageView into the RelativeLayout aligned to PARENT_TOP and PARENT_RIGHT and set the visibility to GONE.
Then you can simply set the ImageView's drawable to the appropriate count image and set the visibility to VISIBLE.
Ok here is what i'd do:
Create a custom control that extends button. I'm not going to do the pretty graphics for you but this will give you the basic idea:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.Button;
public class CounterButton extends Button{
protected int count=0;
protected final Paint myTextPaint = new Paint();
protected final Paint myCirclePaint = new Paint();
public CounterButton(Context context, AttributeSet attrs) {
super(context, attrs);
this.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.ic_dialog_email));
this.myCirclePaint.setARGB(150, 255, 0, 0);
this.myTextPaint.setARGB(150, 255, 255, 255);
}
#Override
protected void onDraw(Canvas canvas) {
if(count!=0){
canvas.drawCircle((float) (this.getWidth()*.75), (float) (this.getHeight()*.4), this.getHeight()/5, myCirclePaint);
canvas.drawText(Integer.toString(count), (float) (this.getWidth()*.75), (float) (this.getHeight()*.4), this.myTextPaint);
}
}
}
Clean up the sizing of your text you draw, the circle positioning (and add a border etc) and you have a custom control. You could further extend it so you could set the background in xml or dynamically and you have a reusable control with a number counter in a circle.
then in your code you could do:
CounterButton cb=(CounterButton) findViewById(R.id.whateverYouGaveItInXML);
cb.count=SomeNewNumber;
cb.invalidate;
the invalidate will redraw the image with the new value in the circle.
I used a button in the event you want to have it clickable easily and all that - but you could just as easily extend view if you are just doing a view.
I'm trying to create a View with rounded corners (and a background color of choice) that I can reuse with different background colors; hard to explain, so here's my code:
/app/src/com/packagename/whatever/CustomDrawableView.java
package com.packagename.whatever;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.PaintDrawable;
import android.util.AttributeSet;
import android.view.View;
public class CustomDrawableView extends View {
private PaintDrawable mDrawable;
int radius;
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.RoundedRect);
radius = a.getInteger(R.styleable.RoundedRect_radius, 0);
}
public CustomDrawableView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
mDrawable = new PaintDrawable();
}
protected void onDraw(Canvas canvas) {
mDrawable.setCornerRadius(radius);
mDrawable.draw(canvas);
}
}
Here's the XML to display the custom component:
/app/res/layout/test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ny="http://schemas.android.com/apk/res/com.packagename.whatever"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:padding="10dp">
<com.packagename.whatever.CustomDrawableView
android:id="#+id/custom"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#b80010"
ny:radius="50"
/>
</LinearLayout>
I'm wanting the red box to have 50px rounded corners, but as you'll see, it does not:
The idea is that I could easily change the background color in the XML and automatically have a nice View with rounded corners, without having to create multiple drawables.
Thanks for the help!
You need to set your corner radius and color into the background drawable.
Here is one way that would work. Grab the color you set in android:background, then use it to create a new drawable that you set into the background in the constructor. This will work as long as you only set android:background to a color value.
public CustomDrawableView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
// pull out the background color
int color = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/android", "background", 0xffffffff);
// create a new background drawable, set the color and radius and set it in place
mDrawable = new PaintDrawable();
mDrawable.getPaint().setColor(color);
mDrawable.setCornerRadius(radius);
setBackgroundDrawable(mDrawable);
}
If you override onDraw, make sure you call super.onDraw(canvas) first to get the background drawn.
given a simple shapedrawable like this:
public ShapeDrawable Sd(int s){
float[] outerR = new float[] { 12, 12, 12, 12, 12, 12, 12, 12 };
ShapeDrawable mDrawable = new ShapeDrawable(new RoundRectShape(outerR, null,null));
mDrawable.getPaint().setColor(s);
return mDrawable;
}
you can do the following:
LinearLayout l=(LinearLayout) findViewById(R.id.testLayout);
l.setBackgroundDrawable(Sd(0xff74AC23));
where the 12's represent the radius.
you could apply this to any view for a background drawable.
Take a look at this question: How do I set the rounded corner radius of a color drawable using xml?
And perhaps also these two:
How to add rounded corner to a drawable I'm using as a background in Android?
How should I give images rounded corners in Android?