Drawing StaticLayout not working correct - java

I've implemented a View with a Text.Layout and I'm drawing it but I'm getting nothing. Here is my code
public class MyEfficientTextView extends View {
Layout textLayout;
SpannableStringBuilder spannableStringBuilder;
int width = 0, height = 0;
TextPaint textPaint;
CharSequence text;
public MyEfficientTextView(Context context) {
this(context, null, 0);
}
public MyEfficientTextView(Context context, #Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyEfficientTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
spannableStringBuilder = new SpannableStringBuilder();
}
public void setText(CharSequence charSequence) {
text = charSequence;
spannableStringBuilder.clear();
spannableStringBuilder.append(text);
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
textPaint.setColor(getResources().getColor(R.color.textColor));
textLayout = new StaticLayout(spannableStringBuilder, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
invalidate();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != width) {
width = w;
setText(text);
}
height = h;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
if (textLayout != null) {
canvas.translate(getPaddingLeft(), getPaddingTop());
textLayout.draw(canvas);
}
canvas.restore();
}
}
this code is just for testing if it's working or not and it's not efficient. Which part is wrong and causing it to not render text?

you should override onMeasure and set the view width/height. your example code will result in default width/height (probably 0).

Related

How can I pass a float value to a class that extends LinearLayout before it gets inflated?

I created a class that extends LinearLayout to draw a border-radius and added the setRadius() function
public class RadiusLinearLayout extends LinearLayout {
private float CORNER_RADIUS;
private Bitmap maskBitmap;
private Paint paint, maskPaint;
private float cornerRadius;
public void setRadius(float cornerRadius) {
CORNER_RADIUS = cornerRadius;
}
public RadiusLinearLayout(Context context) {
super(context);
init(context, null, 0);
}
public RadiusLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public RadiusLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setWillNotDraw(false);
}
#Override
public void draw(Canvas canvas) {
Bitmap offscreenBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas offscreenCanvas = new Canvas(offscreenBitmap);
super.draw(offscreenCanvas);
if (maskBitmap == null) {
maskBitmap = createMask(getWidth(), getHeight());
}
offscreenCanvas.drawBitmap(maskBitmap, 0f, 0f, maskPaint);
canvas.drawBitmap(offscreenBitmap, 0f, 0f, paint);
}
private Bitmap createMask(int width, int height) {
Bitmap mask = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas(mask);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.WHITE);
canvas.drawRect(0, 0, width, height, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRoundRect(new RectF(0, 0, width, height), cornerRadius, cornerRadius, paint);
return mask;
}
}
On the fragment that corresponds to this class, OnCreateView I refence the Layout and set the radius value but it doesn't apply the value I send to the layout
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.calculator_fragment, container, false)
val test = view.findViewById(R.id.testCF) as RadiusLinearLayout
test.setRadius(2f)
return view
}
Is it possible to pass a value to the Layout before the fragment gets inflated?
Is it possible to pass a value to the Layout before the fragment gets inflated?
Basically yes (*), but I'd like to show how to change the radius of your custom View more flexibly:
There are certain changes to a View which make it necessary to update the UI, for example if you call View.setBackground().
In such cases, the View calls invalidate() to let the runtime know that it needs to be redrawn.
Your custom View can do so each time the radius is set:
public void setRadius(float cornerRadius) {
CORNER_RADIUS = cornerRadius;
// Now you need to calculate the field *cornerRadius* once more
//
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics);
invalidate();
}
By the way, setting the corner radius to 2f does not cause that much of a change - I tried with 24f, and it worked fine.
(*) If you absolutely need to change the radius before the custom View is inflated, you can either create it programmatically and call setRadius() before adding it to a ViewGroup.
Or you define a custom attribute to configure the View via the layout file.
Solution 1: It seems you have declared same name of a variable cornerRadius which is declared globally and parameter of setradius method. It created ambiguity. Change the parameter name and do a try.
Something like this
private float cornerRadius;
public void setRadius(float radius) {
CORNER_RADIUS = radius;
}
Solution 2: Pass corner radius value by its constructor
Example:
public RadiusLinearLayout(Context context,float radius) {
super(context);
CORNER_RADIUS = radius;
init(context, null, 0);
}

How to animate the level meter bar?

I am fairly new to Java and android studio. I would like to animate my bar which I created with random value. I have created customView with android studio and drawn a canvas rectangle. Next step is feed some random values and animate the bar. I acheived this feat with (Math.random) in JavaScript.
public class CustomView extends View {
private Rect rect;
private Paint paint;
int radius;
public CustomView(Context context){
super(context);
init(null);
}
public CustomView(Context context, AttributeSet attrs){
super(context, attrs);
init(attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
init(attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(#Nullable AttributeSet set){
rect = new Rect();
// the flag is more pixelated
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
postInvalidate();
}
//drawing a canvas
#Override
protected void onDraw(Canvas canvas){
int viewWidth = getWidth();
int viewHeight = getHeight();
int leftTopX = viewWidth - 750;
int leftTopY = viewHeight - 750;
int rightBotX = viewWidth + 150;
int rightBotY = viewHeight + 150;
paint.setColor(Color.MAGENTA);
canvas.drawRect(leftTopX,leftTopY, rightBotX,rightBotY,paint);
paint.setColor(Color.GREEN);
}
}
This is what I have achieved so far by now.But how can I animate bar with Java ?
Try this -
I used it with seekbar. You can use it for your view. As per your case. You should Progressbar or Seekbar.
seekbar = ((CustomSeekbar) findViewById(R.id.customSeekBar));
seekbar.setMax(2600);
final ObjectAnimator animation = ObjectAnimator.ofInt(seekbar, "progress", 0, 800;
animation.setDuration(1500);
animation.setInterpolator(new DecelerateInterpolator());
animation.start();
seekbar.clearAnimation();
Here 0 is the starting value and 800 is the max value. Please change is as per your requirement.

How to initialize a custom component programmatically in Android?

I already know how to use a custom component in a xml. Now, I'm trying to call one programmatically. I haven't found any tutorial about this so I tried to do it like how we declare a view programmatically. One problem I stumbled upon is that how do I get an AttributeSet that I pass on my constructor? I don't know how I get or create that to pass it for my custom component.
This is my custom component class.
public class CheckOutItem extends ConstraintLayout {
public Context ctx;
public Paint mPaint;
public Rect mRect;
int mSquareColor;
public ImageView imgThumbnail;
public TextView lblAbbrev, lblFullName;
public ConstraintLayout lytMain;
String TAG = "sharePOS";
public CheckOutItem(Context context) {
super(context);
ctx = context;
init(null);
}
public CheckOutItem(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.checkout_item, this);
ctx = context;
init(attrs);
}
public CheckOutItem(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ctx = context;
init(attrs);
}
private void init(#Nullable AttributeSet set){
//inflate(ctx, R.layout.checkout_item, this);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRect = new Rect();
if(set == null){
return;
}
TypedArray ta = getContext().obtainStyledAttributes(set, R.styleable.CheckOutItem);
this.imgThumbnail = findViewById(R.id.imgItemThumbnail);
this.lblAbbrev = findViewById(R.id.lblItemAbbrevName);
this.lblFullName = findViewById(R.id.lblItemFullName);
this.lytMain = findViewById(R.id.lytMain);
this.lblAbbrev.setText(ta.getText(R.styleable.CheckOutItem_abbrevText));
this.lblAbbrev.setTextSize(ta.getDimension(R.styleable.CheckOutItem_abbrevTextSize, 1f));
this.lblAbbrev.setTextColor(ta.getColor(R.styleable.CheckOutItem_abbrevTextColor, Color.BLACK));
this.lblFullName.setText(ta.getText(R.styleable.CheckOutItem_fullNameText));
this.lblFullName.setTextSize(ta.getDimension(R.styleable.CheckOutItem_fullNameTextSize, 1f));
this.lblFullName.setTextColor(ta.getColor(R.styleable.CheckOutItem_fullNameTextColor, Color.BLACK));
this.lblFullName.setBackgroundColor(ta.getColor(R.styleable.CheckOutItem_fullNameBackgroundColor, Color.WHITE));
this.lytMain.setBackgroundColor(ta.getColor(R.styleable.CheckOutItem_mainBackgroundColor, Color.LTGRAY));
this.lytMain.setBackground(ta.getDrawable(R.styleable.CheckOutItem_mainBackgroundDrawable));
ta.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRect.left = 0;
mRect.right = getWidth();
mRect.top = 0;
mRect.bottom = getHeight();
Log.d(TAG, "rect: " + mRect);
canvas.drawRect(mRect, mPaint);
}
}
UPDATE:
How do I set a margin in the custom component? Regular LayoutParams with setMargins() doesn't seem to work.

Different behaviour from custom textview on different android api levels

I have a problem with a custom text view component in android. I use a custom spannable to set the background of my textview as seen in the first image. This works properly on android api level 21 (first image). But if I use api level 25 (second image) the custom textview disappears.
Code from my custom text view
public class MetalTextView extends android.support.v7.widget.AppCompatTextView
{
private final String xmlns = "http://schemas.android.com/apk/res/android";
private Context context;
private AttributeSet attributeSet;
private String value;
public MetalTextView(Context context)
{
super(context);
this.context = context;
value = attributeSet.getAttributeValue(xmlns, "text");
}
public MetalTextView(Context context, #Nullable AttributeSet attrs)
{
super(context, attrs);
this.context = context;
this.attributeSet = attrs;
value = attributeSet.getAttributeValue(xmlns, "text");
}
public MetalTextView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
this.context = context;
this.attributeSet = attrs;
value = attributeSet.getAttributeValue(xmlns, "text");
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
final Spannable spannable = new SpannableString(value);
spannable.setSpan(new RoundedBackgroundSpannable(), 0, value.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
this.setText(spannable);
}
#Override
public void setText(CharSequence text, BufferType type)
{
super.setText(text, type);
value = text.toString();
final Spannable spannable = new SpannableString(text);
spannable.setSpan(new RoundedBackgroundSpannable(), 0, value.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
Code from my custon spannable
public class RoundedBackgroundSpannable extends ReplacementSpan
{
private static int CORNER_RADIUS = 20;
private final int backgroundColor = 0xFFFFFFFF;
private final int textColor = 0xFF000000;
private final int borderColor = 0xFF000000;
private final float strokeWidth = 5f;
public RoundedBackgroundSpannable()
{
super();
}
#Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)
{
final RectF rectF = new RectF(x, top, x + measureText(paint, text, start, end ), bottom);
paint.setColor(backgroundColor);
canvas.drawRoundRect(rectF, CORNER_RADIUS, CORNER_RADIUS, paint);
paint.setColor(textColor);
canvas.drawText(text, start, end, x, y, paint);
paint.setColor(borderColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
final Path path = new Path();
path.addRoundRect(rectF, CORNER_RADIUS, CORNER_RADIUS, Path.Direction.CW);
canvas.drawPath(path, paint);
}
#Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm)
{
return Math.round(paint.measureText(text, start, end));
}
private float measureText(Paint paint, CharSequence text, int start, int end)
{
return paint.measureText(text, start, end);
}
}
My usage of my custom component
<com.metalcrunch.ui.MetalTextView
android:id="#+id/metalTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="#dimen/textview_headline_margin_top"
android:layout_weight="0.1"
android:gravity="center"
android:text="Some Text"
android:textSize="#dimen/textview_headline_text_size"/>
What is the reason that the textview disappears on api level 25?

Is this a good method to make ImageView square?

Parent layout has a specified width value (50dp), and height set to MATCH_PARENT. In that layout I am adding a few ImageView widgets using Java, and because parent layout has specified width, I can set width and height of each ImageView to MATCH_PARENT and using Java I can make that ImageView square by setting its height as width.
This is my code:
public class Icon extends ImageView {
public AppIcon(final Context context)
{
super(context);
}
public AppIcon(final Context context, final AttributeSet attrs)
{
super(context, attrs);
}
public AppIcon(final Context context, final AttributeSet attrs, final int defStyle)
{
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(final int width, final int height)
{
setMeasuredDimension(width, width);
}
}
Is this a good method to make each ImageView square? I think it's too simple, like something is missing.
P.S.: My method works, but I need to be sure, that everything is fine.
Your code is working one but it can be enhanced. See my code:
Updated my code.
The image will be scaled based on the measured width and height and whichever is smaller.
public class Icon extends ImageView {
public Icon(final Context context) {
super(context);
}
public Icon(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public Icon(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int width, int height) {
super.onMeasure(width, height);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
if (measuredWidth > measuredHeight) {
setMeasuredDimension(measuredHeight, measuredHeight);
} else {
setMeasuredDimension(measuredWidth, measuredWidth);
}
}
}

Categories