I'm trying to display the text on custom ImageView using canvas.
This is my TextPaint:
mPaint = new TextPaint();
mPaint.setDither(true);
mPaint.setColor(0xFFFFFFFF);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTypeface(tf);
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(mSize);
User enters his text in EditText which is converted into String(sText) and then into SpannableString(ssText). That SpannableString is being displayed on the canvas.
This is my onDraw:
int x = this.getWidth() / 2, y = 10;
Editable.Factory fac = Editable.Factory.getInstance();
Editable edit = fac.newEditable(ssText);
DynamicLayout layout = new DynamicLayout(edit, mPaint, (x * 2), Alignment.ALIGN_NORMAL, 1, 0, false);
canvas.translate(x, y);
layout.draw(canvas);
canvas.restore();
Now, I'm taking some strings(mText) as input (one string in one line) from user in EditText of which I have to change color in the already displayed SpannableString(ssText) on the canvas.
for (String line : mText.split("\n")) { // one string one line
if (sText.matches(".*\\b" + line + "\\b.*")) {
int len = line.length();
// get the starting index of String line in sText
for (int i = -1; (i = sText.indexOf(mText, i + 1)) != -1;) {
// change the color of required text in ssText
ssText.setSpan(new ForegroundColorSpan(Color.rgb(r_deep, g_deep, b_deep)), i, (i + len), 0);
}
}
}
After changing the color of ssText as above I again call onDraw on the click of ChangeColor button to diaplay it back on the canvas. But, here what I see is that the position of the ssText on canvas is irregular.
I see this before the click of ChangeColor button:
And this is after click of ChangeColor button:
If the problem is in translate of canvas the I'm also using canvas.restore();
I don't understand what the problem is. Please help!
Related
I am developing a small chart app to monitor the blood sugar with the MPAndroidChart library.
Is there a way to draw the text inside the circle without offset?
As suggested in another post, i put in a slight y-offset (different y-data for circle and text) and achieved a result which is ok.
But if there are only two values the text and circle do not align.
I am not that familiar with modifying the yAxis Renderer, do you know how to find the formula behind the offset for the labels? (would be great to just recalculate it..)
Similar problem: How to place the text values inside in MPAndroidChart circle?
With manual offset between text and circle y-data
Due to zooming in on y-axis (less data points and range on y-axis) the manuel offset is not working well here..
Otherwise awesome library PhilJay !!
EDIT: I have found a easy and sufficient solution:
Override the drawValues method from LineChartRenderer**
In LineChartRenderer.java -> drawValues the text is vertically shifted by this line:
drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
So to get rid of the "- valOffset":
1.Override the drawValues method
Create a new java file "CenteredTextLineChartRenderer.java" and override method drawValues from LineChartRenderer
2.Modify the y-valOffset to y+textHeight*0.35f
Add float textHeight = dataSet.getValueTextSize();
public class CenteredTextLineChartRenderer extends LineChartRenderer {
public CenteredTextLineChartRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
//Modified drawValues Method
// Center label on coordinate instead of applying a valOffset
#Override
public void drawValues(Canvas c) {
if (isDrawingValuesAllowed(mChart)) {
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
float textHeight = dataSet.getValueTextSize();
if (!shouldDrawValues(dataSet) || dataSet.getEntryCount() < 1)
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleRadius() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
mXBounds.set(mChart, dataSet);
float[] positions = trans.generateTransformedValuesLine(dataSet, mAnimator.getPhaseX(), mAnimator
.getPhaseY(), mXBounds.min, mXBounds.max);
ValueFormatter formatter = dataSet.getValueFormatter();
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
for (int j = 0; j < positions.length; j += 2) {
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
Entry entry = dataSet.getEntryForIndex(j / 2 + mXBounds.min);
if (dataSet.isDrawValuesEnabled()) {
//drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
drawValue(c, formatter.getPointLabel(entry), x, y+textHeight*0.35f, dataSet.getValueTextColor(j / 2));
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
Utils.drawImage(
c,
icon,
(int)(x + iconsOffset.x),
(int)(y + iconsOffset.y),
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
MPPointF.recycleInstance(iconsOffset);
}
}
}
}
3.Set your own LineChart renderer to your modified drawValues class
LineChart mChart = (LineChart) mainActivity.findViewById(R.id.LineChart);
mChart.setRenderer(new CenteredTextLineChartRenderer(mChart,mChart.getAnimator(),mChart.getViewPortHandler()));
Run your code and manually adapt the 0.35f offset in your CenteredTextLineChartRenderer class
Now your text is always vertically centered!
IMPORTANT: With deleting the valOffset your label is not vertically centered as the text anchor is not in the center of your text label. So you have to insert a manual offset "textHeight*0.35f" (just try it out).
I am trying to create a text sticker in android. I am creating a text sticker on one of my phones and then saving the text size in the DP.
Then on another phone, I am loading the same text with the same DP and converting the DP value to pixel. Then calling the method
TextPaint.setTextSize(pixelValue)
But the output is different on both phones. I also tried replacing DP with SP, but it seems that's not the solution.
Also one more thing, I am determining the width and height of my view based on a drawable(res) width height.
Here is code of that drawable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#android:color/transparent" />
<size android:width="200dp" android:height="50dp" />
</shape>
But this doesn't seem to be problem as the values are in DP in the drawable file.
Here is the constructor of text sticker.
public DynamicTextSticker(#NonNull Context context, #Nullable Drawable drawable, String text) {
this.context = context;
this.drawable = drawable;
if (drawable == null) {
this.drawable = ContextCompat.getDrawable(context, R.drawable.sticker_transparent_background);
}
textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
minTextSizePixels = DensityUtils.px2dp(context, 6);
maxTextSizePixels = DensityUtils.px2dp(context, 32);
alignment = Layout.Alignment.ALIGN_CENTER;
textPaint.setTextSize(maxTextSizePixels);
realBounds = new Rect(0, 0, getWidth(), getHeight());
textRect = new Rect(0, 0, getWidth(), getHeight());
}
draw method
#Override public void draw(#NonNull Canvas canvas) {
Matrix matrix = getMatrix();
canvas.save();
canvas.concat(matrix);
if (drawable != null) {
drawable.setBounds(realBounds);
drawable.draw(canvas);
}
canvas.restore();
canvas.save();
canvas.concat(matrix);
if (textRect.width() == getWidth()) {
int dy = getHeight() / 2 - staticLayout.getHeight() / 2;
// center vertical
canvas.translate(0, dy);
} else {
int dx = textRect.left;
int dy = textRect.top + textRect.height() / 2 - staticLayout.getHeight() / 2;
canvas.translate(dx, dy);
}
staticLayout.draw(canvas);
canvas.restore();
}
here is the resize method where the static layout is initialized
final int availableHeightPixels = textRect.height();
final int availableWidthPixels = textRect.width();
final CharSequence text = getText();
// Safety check
// (Do not resize if the view does not have dimensions or if there is no text)
if (text == null
|| text.length() <= 0
|| availableHeightPixels <= 0
|| availableWidthPixels <= 0
|| maxTextSizePixels <= 0) {
return this;
}
float targetTextSizePixels = maxTextSizePixels;
int targetTextHeightPixels =
getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels);
// Until we either fit within our TextView
// or we have reached our minimum text size,
// incrementally try smaller sizes
while (targetTextHeightPixels > availableHeightPixels
&& targetTextSizePixels > minTextSizePixels) {
targetTextSizePixels = Math.max(targetTextSizePixels - 2, minTextSizePixels);
targetTextHeightPixels =
getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels);
}
// If we have reached our minimum text size and the text still doesn't fit,
// append an ellipsis
// (NOTE: Auto-ellipsize doesn't work hence why we have to do it here)
if (targetTextSizePixels == minTextSizePixels
&& targetTextHeightPixels > availableHeightPixels) {
// Make a copy of the original TextPaint object for measuring
TextPaint textPaintCopy = new TextPaint(textPaint);
textPaintCopy.setTextSize(targetTextSizePixels);
// Measure using a StaticLayout instance
StaticLayout staticLayout =
new StaticLayout(text, textPaintCopy, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL,
lineSpacingMultiplier, lineSpacingExtra, false);
// Check that we have a least one line of rendered text
if (staticLayout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line and add an ellipsis
int lastLine = staticLayout.getLineForVertical(availableHeightPixels) - 1;
if (lastLine >= 0) {
int startOffset = staticLayout.getLineStart(lastLine);
int endOffset = staticLayout.getLineEnd(lastLine);
float lineWidthPixels = staticLayout.getLineWidth(lastLine);
float ellipseWidth = textPaintCopy.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (availableWidthPixels < lineWidthPixels + ellipseWidth) {
endOffset--;
lineWidthPixels =
textPaintCopy.measureText(text.subSequence(startOffset, endOffset + 1).toString());
}
setText(text.subSequence(0, endOffset) + mEllipsis);
}
}
}
textPaint.setTextSize(targetTextSizePixels);
this.targetTextSizePixel = targetTextSizePixels;
staticLayout =
new StaticLayout(this.text, textPaint, textRect.width(), alignment, lineSpacingMultiplier,
lineSpacingExtra, true);
return this;
here is the getWidth and getHeight method
#Override public int getWidth() {
return drawable.getIntrinsicWidth();
}
#Override public int getHeight() {
return drawable.getIntrinsicHeight();
}
I tried several ways but none of them worked for me.
I think the problem is not with text size. but the problem might be with drawable. But I don't know what possibly could go wrong with that, as I have used DP unit everywhere.
**If i forget to add any code please tell me I will attach it as well**
I am new to android so I am not sure the reason behind this problem.
Thanks for your help.
I write a book reader. For the correctly display text on my pages, I need to know, How many characters can be on display ?
TextPaint mTextPaint=new TextPaint();
mTextPaint.setTextSize(16);
StaticLayout mTextLayout = new StaticLayout(newText, mTextPaint, canvas.getWidth() - 20, Alignment.ALIGN_NORMAL, 1.5f, 0.0f, false);
canvas.save();
int x = 10, y = 50;
y += p.ascent() + p.descent();
canvas.translate(x, y);
mTextLayout.draw(canvas);
canvas.restore();
First you need to get last visible line. You can get it with Layout.getLineForVertical method.
After that you can get text offset for line with methods Layout.getLineEnd/Layout.getLineVisibleEnd
Example:
int line = mTextLayout.getLineForVertical(canvas.getHeight()); // position of last visible line
int chars = mTextLayout.getLineEnd(line);
I have some rectangles drawn on Canvas. And now I need for each rectangle an onclickListener.
But because I'm very new to android and specially Canvas, I need some help.
Anyway is it possible to add a listener?
It looks like this:
And this is my code for it:
RelativeLayout ll = (RelativeLayout) findViewById(R.id.rect);
Paint paint = new Paint();
Paint paintForSize = new Paint();
paintForSize.setColor(Color.parseColor("#FFFFFF"));
paintForSize.setTextSize(45);
Bitmap bg = Bitmap.createBitmap(480, 800, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bg);
int left = 0; // initial start position of rectangles (50 pixels from left)
int leftText =70;
int topText = 93;
int top = 50; // 50 pixels from the top
int width = 60;
int height = 60;
for(int x = 0; x < 4; x++) { // draw 4 rectangles
if(x == 0){
paint.setColor(Color.parseColor("#FF7979"));
canvas.drawText("Rec 1", leftText, topText, paintForSize);
}
if(x == 1){
paint.setColor(Color.parseColor("#FFD060"));
canvas.drawText("Rec 2", leftText, topText, paintForSize);
}
if(x == 2){
paint.setColor(Color.parseColor("#B6DB49"));
canvas.drawText("Rec 3", leftText, topText, paintForSize);
}
if(x == 3){
paint.setColor(Color.parseColor("#CB97E5"));
canvas.drawText("Rec 4", leftText, topText, paintForSize);
}
canvas.drawRect(left, top, left+width, top+height, paint);
top = (top + height + 10); // set new left co-ordinate + 10 pixel gap
topText = (topText + height + 10);
}
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
ImageView iV = new ImageView(this);
iV.setImageBitmap(bg);
ll.addView(iV,params);
Can anybody guide me through this, how should I do it?
Canvas is not a View. You cannot add listeners to them. You could create a custom View implementation. Whatever you do with the Canvas, implement that in onDraw(), which receives a Canvas as a parameter. Then add your view to a layout and give it whatever listeners you want. You may as well look into SurfaceView for drawing, which is a View.
This might help you.
Might I suggest rethinking how this is going to work. If these rectangles are going to be buttons of some sort, perhaps your better off drawing buttons in your user interface, or using images instead of the rectangles. (Sorry didnt realise this is what you were going to do in your previous question)
Have a look at: Difference between a clickable ImageView and ImageButton
You can set an eventlistener on your imageview, as mentioned in another answer. See here: Image in Canvas with touch events
How can I get the border width of a stand Android button programmatically? I simply need to resize the text to fit to the gray area and need to position other objects inline with the buttons, but I cannot do that without knowing the size of the border. I need this to work in all API's 7+. The red arrows in the image below show what I am trying to get:
Here is the code I use for creating my button:
cmdView = new Button(this);
params = new RelativeLayout.LayoutParams(widthLblViewVerbs , (int) fieldHeight);
params.leftMargin = (int) (screenWidth - params.width);
params.topMargin = (int) yPos;
cmdView.setSingleLine();
cmdView.setText("View");
cmdView.setPadding(0, 0, 0, 0);
cmdView.setTextSize(TypedValue.COMPLEX_UNIT_PX, SetTextSize(cmdView.getText().toString(), params.width, params.height));
layout.addView(cmdView, params);
NB. I've had to ask this question again because someone downvoted my question last time and I am desperate for a solution. I have made absolutely no progress with my program in weeks because I have been stuck with this and another problem. If there is something unclear about my question, please let me know and I will edit it. Thank you
My initial code recommendation from the comments can be found here. The implementation done by Dan Bray is:
final Button button = new Button(this);
params = new RelativeLayout.LayoutParams(50, 50);
layout.addView(button, params);
button.post(new Runnable()
{
#Override
public void run()
{
button.buildDrawingCache();
Bitmap viewCopy = button.getDrawingCache();
boolean stillBorder = true;
PaddingLeft = 0;
PaddingTop = 0;
while (stillBorder)
{
int color = viewCopy.getPixel(PaddingLeft, button.getHeight() / 2);
if (color != Color.TRANSPARENT)
stillBorder = false;
else
PaddingLeft++;
}
stillBorder = true;
while (stillBorder)
{
int color = viewCopy.getPixel(button.getWidth() / 2, PaddingTop);
if (color != Color.TRANSPARENT)
stillBorder = false;
else
PaddingTop++;
}
}
});
Not really clear on the question - you want the margins of the underlying view and then the set the size of the button to match ? Then Have you tried
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
margins are accessible via
lp.leftMargin;
lp.rightMargin;
lp.topMargin;
lp.bottomMargin;
Details here:http://developer.android.com/reference/android/view/ViewGroup.MarginLayoutParams.html
This gets the width and height of the button's border:
final Button button = new Button(this);
params = new RelativeLayout.LayoutParams(50, 50);
layout.addView(button, params);
button.post(new Runnable()
{
#Override
public void run()
{
button.buildDrawingCache();
Bitmap viewCopy = button.getDrawingCache();
boolean stillBorder = true;
PaddingLeft = 0;
PaddingTop = 0;
while (stillBorder)
{
int color = viewCopy.getPixel(PaddingLeft, button.getHeight() / 2);
if (color != Color.TRANSPARENT)
stillBorder = false;
else
PaddingLeft++;
}
stillBorder = true;
while (stillBorder)
{
int color = viewCopy.getPixel(button.getWidth() / 2, PaddingTop);
if (color != Color.TRANSPARENT)
stillBorder = false;
else
PaddingTop++;
}
}
});
Assuming you are using a nine patch background image, there is a much easier way.
In the nine patch image, you can define a padding box. This padding box will automatically be applied to the contents you set. No need to recalculate the paddings manually in that case.
Note that you should not set a padding on views using a nine patch as a background as the padding from the nine patch will be ignored.