onDraw doesn't get called on custom view? - android

I'm trying to draw a rounded rect with a specific color, but i'm getting nothing.
I've already done lots of googling, and found some questions like this and read them all. However, none of them solved my problem. Why my onDraw method is never called?
Any help is appreciated.
public class RoundedTagItem extends RelativeLayout {
Context ctx;
String colorString;
int color;
public RoundedTagItem(Context context) {
super(context);
this.ctx = context;
color = Color.WHITE;
this.setWillNotDraw(false);
this.setPadding(10, 0, 10, 0);
}
public RoundedTagItem(Context context, String color) {
super(context);
this.ctx = context;
this.colorString = color;
this.setWillNotDraw(false);
this.setPadding(10, 0, 10, 0);
}
#Override
protected void onDraw(Canvas canvas) {
if(colorString != null)
color = Color.parseColor("#" + colorString);
int rectValue = 35;
Log.d("Rounded", "rectValue: " + rectValue);
RectF rect1 = new RectF(0, 0, rectValue, rectValue);
Paint paint = new Paint();
paint.setStrokeWidth(1);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(color);
canvas.save();
canvas.drawRoundRect(rect1, 10, 10, paint);
canvas.restore();
super.onDraw(canvas);
}
public void ChangeBackgroundColor(String colorString) {
color = Color.parseColor(colorString);
invalidate();
}
}
And then I put icons on this custom view on my main class:
// add category items to view
LinearLayout categories = (LinearLayout) findViewById(R.id.detailTagImagesLayout);
for(int i = 0; i < item.getCategoryItems().size(); i++) {
RoundedTagItem v = null;
if(i == 0)
v = new RoundedTagItem(DetailActivity.this,
item.getCategoryItems().get(i).getColorCode());
else
v = new RoundedTagItem(DetailActivity.this);
ImageView img = new ImageView(DetailActivity.this);
img.setImageDrawable(Drawable.createFromPath(
item.getCategoryItems().get(i).getLightIconUrl()));
v.addView(img);
v.setTag(i);
v.setOnClickListener(new TagClickListener());
categories.addView(v);
}

Some improvements/problems you can make/fix...
You can move these lines to your constructor:
if(colorString != null)
color = Color.parseColor("#" + colorString);
Since this colorString never changes and the way you are doing it now you are calculating it every time onDraw is called (and he will be called a lot of times).
Second please move your super.onDraw(canvas) to your first line.
Third you need to define your LayoutParams on your constructor. If a View has 0 width/height its draw will never be called!
this.setLayoutParams(new LayoutParams(150, 150));
At last please make sure you are using your RoundTagItem. You can add this view to your xml using a tag like this: <your.package.RoundTagItem> where your.package is the package that you are using (com.something.blabla). If you use it like this please be sure you define the layout_width and the layout_height.
You can also add your view programmatcly by adding to your root view (you get it by using findViewById(R.layout.your_root)) or by setting your view as the main content.
RoundedTagItem myView = new RoundedTagItem(this);
setContentView(myView);

Try to create a simple single RelativeLayout XML (rounded_tag_item.xml), and inflate the custom view:
public class RoundedTagItem extends RelativeLayout {
public RoundedTagItem(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public RoundedTagItem(Context context) {
super(context);
init(context);
}
private void init(Context context) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.rounded_tag_item, new RelativeLayout(context));
// rest of stuff...
}
}
}

Related

How to force canvas drawn views to be in an exact dimension?

I'm trying to force a drawn view to be in an exact dimension.
No matter how I try it ignores the dimensions.
Code:
My view to be drawn in 250x250 pixels:
public class MyCustomView extends View {
public MyCustomView(Context context) {
super(context);
}
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
FlowLayout layout = new FlowLayout(getContext());
for (int i = 0; i < 30; i++) {
TextView textView = new TextView(getContext());
textView.setText("[text#" + i + "]");
textView.setBackgroundColor(Color.GREEN);
layout.addView(textView);
}
int dimension = 250;
layout.measure(
dimension,
dimension);
layout.layout(0, 0,
dimension,
dimension);
layout.draw(canvas);
}
}
Result:
The layout clearly not renders in 250x250 pixels.
If I swap the FlowLayout to a standard Android SDK layout, like to a Linear Layout, the result is the same.
Please help if you can.

In an Android XML layout, can I use a child view or viewgroup to tint part of the parent's background?

I currently have the following layout as part of my XML file:
LinearLayout (orientation: vertical)
+---TextView (title)
+---LinearLayout (orientation: horizontal)
+---ConstraintLayout
+---...
+---ConstraintLayout
+---...
+---ConstraintLayout
+---...
The parent LinearLayout has a background that is a drawable.
My intention is to have the child TextView and child LinearLayout tint a part of said parent LinearLayout's background each with different colors.
I've tried the backgroundTint and foregroundTint attributes in both the child layouts, but neither changed the parent LinearLayout's drawable in any way.
So, is there any way I can do what I asked in the title, or am I stuck subclassing the LinearLayout and overriding the onDraw function?
Yup, I'm stuck with using the ViewGroup's onDraw function:
public class PartialTintedRoundedRectLinearLayout extends LinearLayout {
Context ctx;
RectF topPartitionRect, botPartitionRect;
RectF topBandAidRect, botBandAidRect;
Paint bgTopPaint, bgBotPaint;
int defaultCornerRadius;
float material30sdp;
public PartialTintedRoundedRectLinearLayout(Context c) {
super(c);
init(c);
}
public PartialTintedRoundedRectLinearLayout(Context c, AttributeSet a) {
super(c, a);
init(c);
}
public PartialTintedRoundedRectLinearLayout(Context c, AttributeSet a, int dsAttr) {
super(c, a, dsAttr);
init(c);
}
private void init(Context c) {
setWillNotDraw(false);
ctx = c;
defaultCornerRadius = (int)(20 * ctx.getResources().getDisplayMetrics().density);
material30sdp = ctx.getResources().getDimension(R.dimen._30sdp);
bgTopPaint = new Paint(); bgTopPaint.setColor(ContextCompat.getColor(ctx, R.color.blue_700));
bgBotPaint = new Paint(); bgBotPaint.setColor(ContextCompat.getColor(ctx, R.color.blue_500));
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
topPartitionRect = new RectF(0, 0, getWidth(), material30sdp);
topBandAidRect = new RectF(0, defaultCornerRadius, getWidth(), material30sdp);
botPartitionRect = new RectF(0, material30sdp, getWidth(), getHeight());
botBandAidRect = new RectF(0, material30sdp, getWidth(), getHeight() - defaultCornerRadius);
invalidate();
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRoundRect(topPartitionRect, defaultCornerRadius, defaultCornerRadius, bgTopPaint);
canvas.drawRoundRect(botPartitionRect, defaultCornerRadius, defaultCornerRadius, bgBotPaint);
canvas.drawRect(topBandAidRect, bgTopPaint);
canvas.drawRect(botBandAidRect, bgBotPaint);
}
}
I'll have to figure out how to make it more general, since this was made for one specific use-case.

How to display divider value below seekbar in Android?

I have added a seekbar to one of my activities.
Its max value is 5. Now, I want to display the divider values (with increment 1, like 0, 1, 2, 3, 4 and 5) below my seekbar. How can I do that?
Is there any system method to achieve this which I am not able to put my hands on? Any inputs are welcomed.
NOTE : I want to apply any changes programatically, not from xml. The numbers should be separated at equal intervals. I could not edit it that precisely though.
I am supposing you want to display view like below in picture.
if that is the case you have to create your own customSeekbar like give code.
CustomSeekBar.java
public class CustomSeekBar extends SeekBar {
private Paint textPaint;
private Rect textBounds = new Rect();
private String text = "";
public CustomSeekBar(Context context) {
super(context);
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
}
public CustomSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
textPaint = new Paint();
textPaint.setTypeface(Typeface.SANS_SERIF);
textPaint.setColor(Color.BLACK);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
textPaint = new Paint();
textPaint.setColor(Color.BLACK);
}
#Override
protected synchronized void onDraw(Canvas canvas) {
// First draw the regular progress bar, then custom draw our text
super.onDraw(canvas);
int progress = getProgress();
text = progress + "";
// Now get size of seek bar.
float width = getWidth();
float height = getHeight();
// Set text size.
textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
textPaint.setTextSize(40);
// Get size of text.
textPaint.getTextBounds(text, 0, text.length(), textBounds);
// Calculate where to start printing text.
float position = (width / getMax()) * getProgress();
// Get start and end points of where text will be printed.
float textXStart = position - textBounds.centerX();
float textXEnd = position + textBounds.centerX();
// Check does not start drawing outside seek bar.
if (textXStart <= 1) textXStart = 20;
if (textXEnd > width) {
textXStart -= (textXEnd - width + 30);
}
// Calculate y text print position.
float yPosition = height;
canvas.drawText(text, textXStart, yPosition, textPaint);
}
public synchronized void setTextColor(int color) {
super.drawableStateChanged();
textPaint.setColor(color);
drawableStateChanged();
}
}
In your Xml file use your custom file like below
<com.waleedsarwar.customseekbar.CustomSeekBar
android:id="#+id/seekbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:max="5"
android:paddingBottom="16dp" />
This is another approach. I am extending a linearlayout. I put seekbar and another linearlayout(layout_5) which contains 6 textviews with 0-1-2-3-4-5. Better option would be creating a dynamic image(get width from seekBar) which has these numbers according to segment count.
I force seekbar's indicator to stop at specific points(6 points in your case). Instead of doing this, it is possible to set seekBar's maximum progress value to 5. It will work, but it will not give a good user experience.
public class SegmentedSeekBar extends LinearLayout {
private int[] preDefinedValues;
private int currentProgressIndex;
private SeekBar seekBar;
private int segmentCount = 5:
public SegmentedSeekBar(Context context) {
this(context, null, 0);
}
public SegmentedSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.seekBarStyle);
}
public SegmentedSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SegmentedSeekBar,
0, 0);
try {
segmentCount =
a.getInt(R.styleable.SegmentedSeekBar_segmentCount, -1);
} finally {
a.recycle();
}
init();
}
public void init() {
//this values will be used when you need to set progress
preDefinedValues = new int[segmentCount];
for(int i = 0; i < preDefinedValues.length; i++) {
preDefinedValues[i] = (100/(segmentCount-1)) * i;
}
//Get layout_5
//which is linearlayout with 6 textviews
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View sliderView = inflater.inflate(
getSliderId(segmentCount), null);
//seekbar already inside the linearlayout
seekBar = (SeekBar)sliderView.findViewById(R.id.seek_bar);
//linear layout is vertically align
//so add your 6 textview linearlayout
addView(sliderView);
seekBar.setOnTouchListener(seekBarTouchListener);
}
private int getSliderId(int size) {
return R.layout.layout_5;
}
//this method sets progress which is seen in UI not actual progress
//It uses the conversion that we did in beginning
public synchronized void setProgress(int progress) {
if(preDefinedValues != null && progress < preDefinedValues.length && progress >= 0) {
seekBar.setProgress(preDefinedValues[progress]);
currentProgressIndex = progress;
}
}
//this listener make sure the right progress is seen in ui
//take action when user finish with changing progress
SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
int index = 0;
for(int i = 0; i < preDefinedValues.length; i++) {
//try to find closest preDefinedvalues by comparing with latest value
if(Math.abs(seekBar.getProgress() - preDefinedValues[i]) < Math.abs(seekBar.getProgress() - preDefinedValues[index])) {
index = i;
}
}
setProgress(index);
}
};
}

Can't set view params to WRAP_CONTENT programatically

I am trying to set a custom view to wrap_content in both height and width programatically. The problem is that no matter what i do it is always presented as match_parent.
This is the view
public class Circle extends View
{
public Circle(Context context) {
super(context);
}
public Circle(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public Circle(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(0xffff0000);
canvas.drawCircle(mmToPx(4), mmToPx(4), mmToPx(4), paint);
}
public float mmToPx(int mm) {
DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
return mm * displayMetrics.xdpi * (1.0f/25.4f);
}
this is the main code
public void addCircle()
{
Circle circle = new Circle(this);
Random r = new Random();
FrameLayout.LayoutParams cParams = new FrameLayout.LayoutParams(-1, -2);
cParams.setMargins(r.nextInt((int) (w - (mmToPx(diam)))), r.nextInt((int) (h - (mmToPx(diam)))), 0, 0);
circle.setLayoutParams(cParams);
circle.setBackgroundColor(0xff00ff00);
Log.v("sd", circle.getLayoutParams().width + "");
circle.setId(R.id.circle);
circle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
count++; scoreTv.setText("Score:\n"+count);
frame.removeView(findViewById(R.id.circle));
addCircle();
}
});
frame.addView(circle);
}
Thank you for help :)
First- NEVER use magic numbers like -2. Use ViewGroup.LayoutParams.WRAP_CONTENT. Otherwise your code becomes difficult to read and may contain bugs (for example, if they changed the value).
Second- you aren't actually using cParams anywhere. You want to use the version of addView that takes the view and the layout params. Otherwise how will it know to use those layout params with that view.
Third- you don't have an onMeasure function in your view. Without that, how will it know how big your view is so it can actually wrap content on it? You'll need to define onMeasure so it can get the width and height of your view.
try this:
view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))

How to outline a TextView?

What I want to do? (blue will be changed as white)
What I did?
I have found a class which extends TextView that able to outline textview very close to what I want. The problem is that I could not change stroke color to any color, it draws always as black. How to set border color as white?
What is my output:
Where are my codes?
public class TypeFaceTextView extends TextView {
private static Paint getWhiteBorderPaint(){
Paint p = new Paint(Color.WHITE);
return p;
}
private static final Paint BLACK_BORDER_PAINT = getWhiteBorderPaint();
static {
BLACK_BORDER_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
#Override
public void setText(CharSequence text, BufferType type) {
super.setText(String.format(text.toString()), type);
}
private static final int BORDER_WIDTH = 1;
private Typeface typeface;
public TypeFaceTextView(Context context) {
super(context);
}
public TypeFaceTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setDrawingCacheEnabled(false);
setTypeface(attrs);
}
private void setTypeface(AttributeSet attrs) {
final String typefaceFileName = attrs.getAttributeValue(null, "typeface");
if (typefaceFileName != null) {
typeface = Typeface.createFromAsset(getContext().getAssets(), typefaceFileName);
}
setTypeface(typeface);
}
public TypeFaceTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setTypeface(attrs);
}
#Override
public void draw(Canvas aCanvas) {
aCanvas.saveLayer(null, BLACK_BORDER_PAINT, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
drawBackground(aCanvas, -BORDER_WIDTH, -BORDER_WIDTH);
drawBackground(aCanvas, BORDER_WIDTH + BORDER_WIDTH, 0);
drawBackground(aCanvas, 0, BORDER_WIDTH + BORDER_WIDTH);
drawBackground(aCanvas, -BORDER_WIDTH - BORDER_WIDTH, 0);
aCanvas.restore();
super.draw(aCanvas);
}
private void drawBackground(Canvas aCanvas, int aDX, int aDY) {
aCanvas.translate(aDX, aDY);
super.draw(aCanvas);
}
}
I found simple way to outline view without inheritance from TextView.
I had wrote simple library that use Android's Spannable for outlining text.
This solution gives possibility to outline only part of text.
Library: OutlineSpan
Class (you can copy only class):OutlineSpan
1) create your textview object extends TextView
public class YourTextView extends TextView { .........
2) Do this on its draw method
#Override
public void draw(Canvas canvas) {
for (int i = 0; i < 5; i++) {
super.draw(canvas);
}
}
3) set textview's xml side as below
android:shadowColor="#color/white"
android:shadowRadius="5"
My solution based on custom TextView.
public class TextViewOutline extends TextView{
int outline_color;
float outline_width; //relative to font size
public TextViewOutline( Context context, int outline_color, float outline_width ){
super( context );
this.outline_color = outline_color;
this.outline_width = outline_width;
}
#Override
protected void onDraw( Canvas canvas) {
//draw standard text
super.onDraw( canvas );
//draw outline
Paint paint = getPaint();
paint.setStyle( Paint.Style.STROKE );
paint.setStrokeWidth( paint.getTextSize()*outline_width );
int color_tmp = paint.getColor();
setTextColor( outline_color );
super.onDraw( canvas );
//restore
setTextColor( color_tmp );
paint.setStyle( Paint.Style.FILL );
}
}
Code for test.
TextViewOutline tv = new TextViewOutline( this, 0xff0080ff, 0.04f);
tv.setText( "Simple TEST" );
tv.setTypeface( Typeface.create( Typeface.SERIF, Typeface.BOLD ) );
tv.setTextColor( 0xff000000 );
tv.setTextSize( 128 );
setContentView(tv);
tv.setOnClickListener( new View.OnClickListener(){
#Override
public void onClick( View view ){
view.invalidate();
}
} );
Result.
Couldn't this this but try experimenting with: PorterDuff.Mode
http://developer.android.com/reference/android/graphics/PorterDuff.Mode.html
Try changing it to 'ADD' or 'CLEAR', hope this helps.
You need to change your getWhiteBorderPaint() method to the following:
private static Paint getWhiteBorderPaint(){
Paint p = new Paint();
p.setColor(Color.WHITE);
return p;
}
The Paint constructor only takes bitmasked flags and doesn't support arbitrary ints as parameters.
Investigated into the original problem stated by this question. Found the solution.
First, change DST_OUT to DARKEN
static {
BLACK_BORDER_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
}
Secondly, save the original text color, and put the intended outline color up, draw the outline, and then restore the original text color.
#Override
public void draw(Canvas aCanvas) {
int originalColor = this.getCurrentTextColor();
this.setTextColor(0xff000000); //set it to white.
aCanvas.saveLayer(null, borderPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
drawBackground(aCanvas, -BORDER_WIDTH, -BORDER_WIDTH);
drawBackground(aCanvas, BORDER_WIDTH + BORDER_WIDTH, 0);
drawBackground(aCanvas, 0, BORDER_WIDTH + BORDER_WIDTH);
drawBackground(aCanvas, -BORDER_WIDTH - BORDER_WIDTH, 0);
this.setTextColor(originalColor);
aCanvas.restore();
super.draw(aCanvas);
}
text outline with transparent background
this is one way to do it with no background color
public class CustomTextView extends androidx.appcompat.widget.AppCompatTextView {
float mStroke;
public CustomTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomTextView);
mStroke=a.getFloat(R.styleable.CustomTextView_stroke,1.0f);
a.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
TextPaint paint = this.getPaint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mStroke);
super.onDraw(canvas);
}
}
then you only need to add the following to the attrs.xml file
<declare-styleable name="CustomTextView">
<attr name="stroke" format="float"/>
</declare-styleable>
and now you will be able to set the stroke widht by app:stroke while retaining all other desirable properties of TextView. my solution only draws the stroke w/o a fill. this makes it a bit simpler than the others. bellow a screencapture with the result while setting a custom font to my customtextview on a dark background.

Categories

Resources