How would I extend TextView to allow the drawing of text with a gradient effect?
TextView secondTextView = new TextView(this);
Shader textShader=new LinearGradient(0, 0, 0, 20,
new int[]{Color.GREEN,Color.BLUE},
new float[]{0, 1}, TileMode.CLAMP);
secondTextView.getPaint().setShader(textShader);
I have used the top answer(#Taras) with a gradient of 5 colors, but there is a problem: the textView looks like that I have put a white cover on it. Here is my code and the screenshot.
textView = (TextView) findViewById(R.id.main_tv);
textView.setText("Tianjin, China".toUpperCase());
TextPaint paint = textView.getPaint();
float width = paint.measureText("Tianjin, China");
Shader textShader = new LinearGradient(0, 0, width, textView.getTextSize(),
new int[]{
Color.parseColor("#F97C3C"),
Color.parseColor("#FDB54E"),
Color.parseColor("#64B678"),
Color.parseColor("#478AEA"),
Color.parseColor("#8446CC"),
}, null, Shader.TileMode.CLAMP);
textView.getPaint().setShader(textShader);
After many hours, I found out that I need to call textView.setTextColor() with the first color of the gradient. Then the screenshot:
Hope help someone!
It doesn't appear possible to extend TextView to draw text with a gradient. It is, however, possible to achieve this effect by creating a canvas and drawing on it. First we need to declare our custom UI element. In the initiation we need to create a subclass of Layout. In this case, we will use BoringLayout which only supports text with a single line.
Shader textShader=new LinearGradient(0, 0, 0, 20,
new int[]{bottom,top},
new float[]{0, 1}, TileMode.CLAMP);//Assumes bottom and top are colors defined above
textPaint.setTextSize(textSize);
textPaint.setShader(textShader);
BoringLayout.Metrics boringMetrics=BoringLayout.isBoring(text, textPaint);
boringLayout=new BoringLayout(text, textPaint, 0, Layout.Alignment.ALIGN_CENTER,
0.0f, 0.0f, boringMetrics, false);
We then override onMeasure and onDraw:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
setMeasuredDimension((int) textPaint.measureText(text), (int) textPaint.getFontSpacing());
}
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
boringLayout.draw(canvas);
}
Our implementation of onDraw is at this point quite lazy (it completely ignores the measurement specs!, but so long as you guarantee that the view is given sufficent space, it should work okay.
Alternatively, it would be possible to inherit from a Canvas and override the onPaint method. If this is done, then unfortunately the anchor for text being drawn will always be on the bottom so we have to add -textPaint.getFontMetricsInt().ascent() to our y coordinate.
Here it is with multiline support as a one liner. This should work for Buttons too.
Shader shader = new LinearGradient(0,0,0,textView.getLineHeight(),
startColor, endColor, Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
I've rolled up a library that encompasses both of these methods. You can create GradientTextView in XML or just use GradientTextView.setGradient(TextView textView...) to do it on a regular TextView object.
https://github.com/koush/Widgets
A simple but somewhat limited solution would be to use these attributes:
android:fadingEdge="horizontal"
android:scrollHorizontally="true"
I have used it on textfields where I want them to fade out if they get too long.
Kotlin + coroutines version.
Extension for setting vertical gradient:
private fun TextView.setGradientTextColor(vararg colorRes: Int) {
val floatArray = ArrayList<Float>(colorRes.size)
for (i in colorRes.indices) {
floatArray.add(i, i.toFloat() / (colorRes.size - 1))
}
val textShader: Shader = LinearGradient(
0f,
0f,
0f,
this.height.toFloat(),
colorRes.map { ContextCompat.getColor(requireContext(), it) }.toIntArray(),
floatArray.toFloatArray(),
TileMode.CLAMP
)
this.paint.shader = textShader
}
Suspend extension. You need to wait for the view to change its height.
suspend fun View.awaitLayoutChange() = suspendCancellableCoroutine<Unit> { cont ->
val listener = object : View.OnLayoutChangeListener {
override fun onLayoutChange(
view: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
view?.removeOnLayoutChangeListener(this)
cont.resumeWith(Result.success(Unit))
}
}
addOnLayoutChangeListener(listener)
cont.invokeOnCancellation { removeOnLayoutChangeListener(listener) }
}
And usage:
lifecycle.coroutineScope.launch {
binding.tvAmount.text = "Dumb text"
binding.tvAmount.awaitLayoutChange()
binding.tvAmount.setGradientTextColor(
R.color.yellow,
R.color.green
)
}
For Kotlin:
val paint: TextPaint = textView.paint
val width: Float = paint.measureText(holder.langs.text.toString())
val textShader: Shader = LinearGradient(0f, 0f, width, holder.langs.textSize, intArrayOf(
Color.parseColor("#8913FC"),
Color.parseColor("#00BFFC")), null, Shader.TileMode.CLAMP)
holder.langs.paint.shader = textShader
Here's my solved way. Implement with text span.
screenshot
class LinearGradientForegroundSpan extends CharacterStyle implements UpdateAppearance {
private int startColor;
private int endColor;
private int lineHeight;
public LinearGradientForegroundSpan(int startColor, int endColor, int lineHeight) {
this.startColor = startColor;
this.endColor = endColor;
this.lineHeight = lineHeight;
}
#Override
public void updateDrawState(TextPaint tp) {
tp.setShader(new LinearGradient(0, 0, 0, lineHeight,
startColor, endColor, Shader.TileMode.REPEAT));
}
}
Styled your gradient text.
SpannableString gradientText = new SpannableString("Gradient Text");
gradientText.setSpan(new LinearGradientForegroundSpan(Color.RED, Color.LTGRAY, textView.getLineHeight()),
0, gradientText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(gradientText);
sb.append(" Normal Text");
textView.setText(sb);
Here's a nice way to do it:
/**
* sets a vertical gradient on the textView's paint, so that on its onDraw method, it will use it.
*
* #param viewAlreadyHasSize
* set to true only if the textView already has a size
*/
public static void setVerticalGradientOnTextView(final TextView tv, final int positionsAndColorsResId,
final boolean viewAlreadyHasSize) {
final String[] positionsAndColors = tv.getContext().getResources().getStringArray(positionsAndColorsResId);
final int[] colors = new int[positionsAndColors.length];
float[] positions = new float[positionsAndColors.length];
for (int i = 0; i < positionsAndColors.length; ++i) {
final String positionAndColors = positionsAndColors[i];
final int delimeterPos = positionAndColors.lastIndexOf(':');
if (delimeterPos == -1 || positions == null) {
positions = null;
colors[i] = Color.parseColor(positionAndColors);
} else {
positions[i] = Float.parseFloat(positionAndColors.substring(0, delimeterPos));
String colorStr = positionAndColors.substring(delimeterPos + 1);
if (colorStr.startsWith("0x"))
colorStr = '#' + colorStr.substring(2);
else if (!colorStr.startsWith("#"))
colorStr = '#' + colorStr;
colors[i] = Color.parseColor(colorStr);
}
}
setVerticalGradientOnTextView(tv, colors, positions, viewAlreadyHasSize);
}
/**
* sets a vertical gradient on the textView's paint, so that on its onDraw method, it will use it. <br/>
*
* #param colors
* the colors to use. at least one should exist.
* #param tv
* the textView to set the gradient on it
* #param positions
* where to put each color (fraction, max is 1). if null, colors are spread evenly .
* #param viewAlreadyHasSize
* set to true only if the textView already has a size
*/
public static void setVerticalGradientOnTextView(final TextView tv, final int[] colors, final float[] positions,
final boolean viewAlreadyHasSize) {
final Runnable runnable = new Runnable() {
#Override
public void run() {
final TileMode tile_mode = TileMode.CLAMP;
final int height = tv.getHeight();
final LinearGradient lin_grad = new LinearGradient(0, 0, 0, height, colors, positions, tile_mode);
final Shader shader_gradient = lin_grad;
tv.getPaint().setShader(shader_gradient);
}
};
if (viewAlreadyHasSize)
runnable.run();
else
runJustBeforeBeingDrawn(tv, runnable);
}
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
runnable.run();
return true;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
Also, if you wish to use a bitmap of the gradient, instead or a real one, use:
/**
* sets an image for the textView <br/>
* NOTE: this function must be called after you have the view have its height figured out <br/>
*/
public static void setBitmapOnTextView(final TextView tv, final Bitmap bitmap) {
final TileMode tile_mode = TileMode.CLAMP;
final int height = tv.getHeight();
final int width = tv.getWidth();
final Bitmap temp = Bitmap.createScaledBitmap(bitmap, width, height, true);
final BitmapShader bitmapShader = new BitmapShader(temp, tile_mode, tile_mode);
tv.getPaint().setShader(bitmapShader);
}
EDIT: Alternative to runJustBeforeBeingDrawn: https://stackoverflow.com/a/28136027/878126
I have combined the answers from this thread and made a lightweight library. You can use it with gradle implementation, or simply use the files needed by adding it to your source.
https://github.com/veeyaarVR/SuperGradientTextView
The solution that worked for me is to apply a text color before applying any shaders. As the author of the question posted:
After many hours, I found out that I need to call textView.setTextColor() with the first color of the gradient. Then the screenshot:
What works is to have, for instance, a white color setup as text color in the first place. Then we can apply the shader, and it will be applied on top of the white so we will get the desired gradient color.
Here is an example for linearlayout, you can use this example for textview too, and in the source code there wont be gradient coding, you get the source code and add the code from that site itself - http://android-codes-examples.blogspot.com/2011/07/design-linearlayout-or-textview-and-any.html
I've found the way to do this without the TextView class extension.
class MainActivity : AppCompatActivity() {
private val textGradientOnGlobalLayoutListener = object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
textGradient.paint.shader = LinearGradient(0f, 0f,
textGradient.width.toFloat(),
textGradient.height.toFloat(),
color0, color1, Shader.TileMode.CLAMP)
textGradient.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
private val textGradient by lazy {
findViewById<TextView>(R.id.text_gradient)
}
private val color0 by lazy {
ContextCompat.getColor(applicationContext, R.color.purple_200)
}
private val color1 by lazy {
ContextCompat.getColor(applicationContext, R.color.teal_200)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textGradient.viewTreeObserver.addOnGlobalLayoutListener(textGradientOnGlobalLayoutListener)
}
}
Try
import com.sanne.MultiColorTextView;
MultiColorTextView textview= new MultiColorTextView(this);
textview.setText("SOME TEXT");
textview.setTextColor(/*INT ARRAY WITH YOUR COLOURS*/ );
The program sets a gradient colour across the textview and you can also set separate colours for particular text using
multiColorTextView.colorAll("A word");
MutliColorTextView from https://www.github.com/sanneemmanuel/MultiColorTextView
Related
I am trying to set the background of a view to have a gradient whose color is generated from the Palette api
The gradient will go from a solid and fade out but I want the solid portion to take up a majority of the background. Right now it starts solid and then gradually fades out over the view width, I want it to where it will start fading out from around the center of the view width.
Here is what I do
Palette.from(resource!!.toBitmap()).generate {
if (it != null) {
val paletteColor = it.getDarkVibrantColor("#000000".toColorInt())
val gradientDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT,
intArrayOf(colorWithAlpha(paletteColor, 0f), colorWithAlpha(paletteColor, 1.0f))
)
gradientDrawable.cornerRadius = 0f
_contentTextBackground.background = gradientDrawable
}
}
Is there a way to set the gradient to start further away from the end of the view?
You can do this via XML to change centerX and centerY attributes of GradientDrawable. But sadly, with GradientDrawable, this is not possible programmatically as discussed on Google Issue Tracker.
For APIs below 29, try using a ShapeDrawable with a LinearGradient Shader as a background. This will give you fine-level control over the transition of the colors.
For API 29+, GradientDrawable allows for similar fine-level control over color transitions with setColors().
private fun getBackgroundGradient(width: Int): Drawable {
val colors = intArrayOf(Color.BLUE and 0x00FFFFFF, Color.BLUE, Color.BLUE)
val offsets = floatArrayOf(0.0f, 0.7f, 1.0f)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Oddly, there is no constructor that accepts the offsets.
GradientDrawable().apply {
orientation = GradientDrawable.Orientation.LEFT_RIGHT
setColors(colors, offsets)
}
} else {
val shader: Shader = LinearGradient(
0f, 0f, width.toFloat(), 1f, colors, offsets, Shader.TileMode.CLAMP
)
val shape = ShapeDrawable(RectShape())
shape.paint.shader = shader
shape
}
}
I used 0.7f as the "center" to make a better (IMO) color transition near the 50% mark, but that value could easily be 0.5f or any other value between 0f and 1.0f.
In the following image, the horizontal bar is the width of the screen and is just a View. The vertical red line splits the screen into two to mark the transition.
As stated by #Abdul Mateen you cannot change the attributes in Graient Drawable. This is only possible if you do it via XML. But there is a workaround:
You could theoretically split your background view in half. Then you have two views on which you could then change the color, based on the Palette Library. One view would then be the solid color and the other one would have to have the Gradient in the Background. If you configure that correctly, you should have a perfect gradient from the middle.
Try extending GradientDrawable and set fades and colour as per you needs.
package com.example.testApp;
import android.app.Activity;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.view.View;
public class TetApp extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View v = findViewById(R.id.btn);
v.setBackgroundDrawable( new DrawableGradient(new int[] { 0xff666666, 0xff111111, 0xffffffff }, 0).SetTransparency(10));
}
public class DrawableGradient extends GradientDrawable {
DrawableGradient(int[] colors, int cornerRadius) {
super(GradientDrawable.Orientation.TOP_BOTTOM, colors);
try {
this.setShape(GradientDrawable.RECTANGLE);
this.setGradientType(GradientDrawable.LINEAR_GRADIENT);
this.setCornerRadius(cornerRadius);
} catch (Exception e) {
e.printStackTrace();
}
}
public DrawableGradient SetTransparency(int transparencyPercent) {
this.setAlpha(255 - ((255 * transparencyPercent) / 100));
return this;
}
}
}
reference from this: https://stackoverflow.com/a/5241693/14964046
Just add the solid color in array one more
like below
val gradientDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT,
intArrayOf(colorWithAlpha(paletteColor, 0f), colorWithAlpha(paletteColor, 1.0f), colorWithAlpha(paletteColor, 1.0f))
)
Its not elegant, but works :-D
What I ended up doing was what Mike suggested in the comments was to just add more of the solid color the the color array of the GradientDrawable
so it looked like this
val gradientDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT,
intArrayOf(colorWithAlpha(paletteColor, 0f), colorWithAlpha(paletteColor, 1.0f), colorWithAlpha(paletteColor, 1.0f), colorWithAlpha(paletteColor, 1.0f))
)
So I know there are a lot of questions like this, but none of the answers have had any effect.
I have a custom View that I am trying to put into a ScrollView (all programmatically). The ScrollView itself works fine, is where it needs to be, does what it's supposed to do and when tested by adding a TextView to it, still did what it was supposed to.
However when I create my custom View the view doesn't draw and it is as if nothing is there. The draw(Canvas canvas) method is never called (also tried with onDraw even though I don't know what the difference is but it still didn't work).
I have tried things such as setWillNotDraw(false) in the constructor, called View.invalidate() every time I want it to draw/redraw, nothing appears to be working.
Here is the relevant code:
ScrollView Initialized
sv = new ScrollView(env.getContext());
ScaledBounds sb = UsefulMethods.getScaledBounds(50, 200, BBEnvironment.WIDTH - 100, BBEnvironment.HEIGHT - 300);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(sb.getWidth(), sb.getHeight());
sv.setTranslationX(sb.getX());
sv.setTranslationY(sb.getY());
sv.setLayoutParams(params);
sv.setScrollbarFadingEnabled(false);
sv.setScrollBarSize(0);
sv.setBackgroundColor(Color.argb(150, 0, 255, 0));
Custom View Class
private class LevelSelectionView extends View {
private ArrayList<LevelButton> levelButtons;
public LevelSelectionView(Context context) {
super(context);
levelButtons = new ArrayList<LevelButton>();
int x = 20;
int y = 20;
for(int i = 0; i < 20; i++) {
levelButtons.add(new LevelButton(x, y, 100, 100, 10, i + 1));
levelButtons.get(i).unlock();
x += 100 + 20;
}
setWillNotDraw(false);
}
public void draw(Canvas canvas) {
super.draw(canvas);
System.out.println("is being called"); //except it isn't
Paint paint = new Paint();
paint.setAntiAlias(env.getCurrentSettings().getAntialias());
for(LevelButton lb : levelButtons)
lb.drawObject(canvas, paint);
}
}
Creating and adding custom view
LevelSelectionView lsView = new LevelSelectionView(sv.getContext());
lsView.setLayoutParams(new RelativeLayout.LayoutParams(sv.getLayoutParams().width, 1000));
lsView.setBackgroundColor(Color.argb(150, 0, 0, 255));
sv.addView(lsView);
Why is it not drawing and how do I get it to draw?
I tried like that and it works:
public class LevelSelectionView extends View {
private final static String TAG = LevelSelectionView.class.getSimpleName();
private final ArrayList<Button> mButtons;
private final Paint mPaint;
public LevelSelectionView(Context context) {
super(context);
mButtons = new ArrayList<Button>();
int x = 20;
int y = 20;
mPaint = new Paint();
// mPaint.setAntiAlias(env.getCurrentSettings().getAntialias());
Button myButton;
for (int i = 0; i < 20; i++) {
myButton = new Button(context);
myButton.setLayoutParams(new ViewGroup.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
mButtons.add(myButton);
// mButtons.get(i).unlock();
// x += 100 + 20;
}
setWillNotDraw(false);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Button lb : mButtons)
Log.d(TAG, "is being called"); //except it isn't
// lb.drawObject(canvas, mPaint);
}
}
I don't know what is your LevelButton as it's a custom class.
I create the LevelSelectionView like that, using a FrameLayout but it's the same idea with the ScrollView:
FrameLayout container = (FrameLayout) findViewById(R.id.container);
LevelSelectionView view = new LevelSelectionView(this);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
container.addView(view);
Maybe your issue comes from the LevelButton or the context you use (as you use the Context from env to create the ScrollView and the Context of the ScrollView to create your custom class). You code is not clear as we don't know what is your env object as all your class are inner classes.
It looks like you need to properly implement onMeasure so the scrollview knows how tall that view is.
I want to create a single drawable that shows two lines of text, one above the other. Each line of text has to be in it's own typeface and textsize and it has to create a single drawable because I want to then set it as the drawable for a floating action button.
private void updateFloatingButtonText(String headlineText, String subHeadlineText, FloatingActionButton floatingActionButton) {
int headlineTextSize = getResources().getDimensionPixelSize(R.dimen.headlineTextSize);
int subheadlineTextSize = getResources().getDimensionPixelSize(R.dimen.subheadlineTextSize);
Spannable spannableStringHeadline = new SpannableString(headlineText);
Spannable spannableStringSubheadline = new SpannableString(subHeadlineText);
CustomTypefaceSpan boldSpan = new CustomTypefaceSpan("FontOne", FontCache.get("FontOne.ttf", this));
CustomTypefaceSpan regularSpan = new CustomTypefaceSpan("FontTwo", FontCache.get("FontTwo.ttf", this));
// set typeface headline
spannableStringHeadline.setSpan(regularSpan, 0,
headlineText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
// set typeface subtitle
spannableStringSubheadline.setSpan(boldSpan, 0,
subHeadlineText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
// set text size headline
spannableStringHeadline.setSpan(new AbsoluteSizeSpan(headlineTextSize), 0,
headlineText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
// set text size subline
spannableStringSubheadline.setSpan(new AbsoluteSizeSpan(subheadlineTextSize), 0,
subHeadlineText.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
String finalString = TextUtils.concat(spannableStringHeadline, "\n", spannableStringSubheadline);
floatingActionButton.setImageDrawable([put the resulting drawable here]);
}
I've written this method that creates a single string formatted exactly the way that I need it, but I still have the issue of creating a drawable out of it.
I've tried to use this third party library, but although it displays the text in the correct typefaces it doesn't change the textsize of the lines of text.
https://github.com/devunwired/textdrawable
Is there a trivial (or nontrivial) way of doing this?
Solved by creating a new class that looks like this:
public class TextToDrawable extends Drawable {
private String headlineText = "";
private String subHeadlineText = "";
private final TextPaint headlinePaint = new TextPaint();
private final TextPaint subHeadlinePaint = new TextPaint();
public TextToDrawable(Context context, String headlineText, String subHeadlineText) {
this.headlineText = headlineText;
headlinePaint.setAntiAlias(true);
headlinePaint.setTypeface(FontCache.get("FontA.ttf", context));
headlinePaint.setTextSize(context.getResources().getDimensionPixelSize(R.dimen.headlineTextSize));
headlinePaint.setColor(ContextCompat.getColor(context, android.R.color.white));
headlinePaint.setStyle(Paint.Style.FILL);
headlinePaint.setTextAlign(Paint.Align.LEFT);
this.subHeadlineText = subHeadlineText;
subHeadlinePaint.setAntiAlias(true);
subHeadlinePaint.setTypeface(FontCache.get("FontB.ttf", context));
subHeadlinePaint.setTextSize(context.getResources().getDimensionPixelSize(R.dimen.subheadlineTextSize));
subHeadlinePaint.setColor(ContextCompat.getColor(context, android.R.color.white));
subHeadlinePaint.setStyle(Paint.Style.FILL);
subHeadlinePaint.setTextAlign(Paint.Align.LEFT);
}
#Override
public void draw(Canvas canvas) {
Rect headlineWidth = new Rect();
Rect subheadlineWidth = new Rect();
headlinePaint.getTextBounds(headlineText, 0, headlineText.length(), headlineWidth);
subHeadlinePaint.getTextBounds(subHeadlineText, 0, subHeadlineText.length(), subheadlineWidth);
Rect bounds = new Rect();
headlinePaint.getTextBounds(headlineText, 0, headlineText.length(), bounds);
int x = getBounds().width() / 2 - (headlineWidth.width()/2);
int y = (getBounds().height() / 2);
canvas.drawText(headlineText, x, y, headlinePaint);
x = getBounds().width()/2 - (subheadlineWidth.width()/2);
y += headlinePaint.getFontSpacing();
canvas.drawText(subHeadlineText, x, y, subHeadlinePaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return 0;
}
}
Then using the new class like this:
mFloatingActionButton.setImageDrawable(new TextToDrawable(this, "Headline", "Subheadline"));
This isn't a great solution because it only supports two lines of text - there's nothing dynamic going on here. However, I suppose it would be fairly easy to rewrite to support even more lines and more fonts and it solves the current problem.
By following this question, I was able to have text around an image. However, I have the following problem.
As you can see, the space for the image on top is displayed in every paragraph at the right. In the question someone had this problem and suggested to change 'ss.length()' for 'lines'. This seemed to work except if the first paragraph was too short, the next paragraph would overlap the image.
I modified the FlowTextHelper class slightly to use text from Html. This is the code I'm using:
public class FlowTextHelper {
private static boolean mNewClassAvailable;
/* class initialization fails when this throws an exception */
static {
try {
Class.forName("android.text.style.LeadingMarginSpan$LeadingMarginSpan2");
mNewClassAvailable = true;
} catch (Exception ex) {
mNewClassAvailable = false;
}
}
public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display, int addPadding){
// There is nothing I can do for older versions, so just return
if(!mNewClassAvailable) return;
// Get height and width of the image and height of the text line
thumbnailView.measure(display.getWidth(), display.getHeight());
int height = thumbnailView.getMeasuredHeight();
int width = thumbnailView.getMeasuredWidth() + addPadding;
messageView.measure(width, height); //to allow getTotalPaddingTop
int padding = messageView.getTotalPaddingTop();
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
int lines = (int)Math.round((height - padding) / textLineHeight);
//SpannableString ss = new SpannableString(text);
//For an html text you can use this line:
if(!text.equals("")) {
SpannableStringBuilder ss = (SpannableStringBuilder) Html.fromHtml(text);
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);
messageView.setText(ss);
messageView.setMovementMethod(LinkMovementMethod.getInstance()); // links
// Align the text with the image by removing the rule that the text is to the right of the image
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) messageView.getLayoutParams();
int[] rules = params.getRules();
rules[RelativeLayout.RIGHT_OF] = 0;
}
}
}
public class MyLeadingMarginSpan2 implements LeadingMarginSpan.LeadingMarginSpan2 {
private int margin;
private int lines;
public MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
#Override
public int getLeadingMargin(boolean first) {
return first ? margin : 0;
}
#Override
public int getLeadingMarginLineCount() {
return lines;
}
#Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom, CharSequence text,
int start, int end, boolean first, Layout layout) {}
}
What is causing the space being repeated every paragraph and how can I get rid of it? Any help is appreciated.
I've spend hours to solve this issue, but solved it with thanks to the answer found here:
text wrapping around image in android
Basically as follows:
First add a margin to your textview and set the text
final RelativeLayout.LayoutParams params = RelativeLayout.LayoutParams)messageView.getLayoutParams();
params.setMargins(marginWidth, 0, 0, 0);
messageView.setText(Html.fromHtml(text));
Then add an OnGlobalLayoutListener and in the onGlobalLayout() call you calculate how many lines actually need the margin. You split the lines in 2 separate spannables and add the Margin only to the first one:
messageView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
int linesCount = messageView.getLayout().getLineCount();
// restore the margin
params.setMargins(0, 0, 0, 0);
SpannableString spanS = new SpannableString ( Html.fromHtml(text) );
if (linesCount <= lines) {
spanS.setSpan(new MyLeadingMarginSpan2(lines, width), 0, spanS.length(), 0);
messageView.setText(spanS);
} else {
// find the breakpoint where to break the String.
int breakpoint = messageView.getLayout().getLineEnd(lines-1);
Spannable s1 = new SpannableStringBuilder(spanS, 0, breakpoint);
s1.setSpan(new MyLeadingMarginSpan2(lines, width), 0, s1.length(), 0);
Spannable s2 = new SpannableStringBuilder(System.getProperty("line.separator"));
Spannable s3 = new SpannableStringBuilder(spanS, breakpoint, spanS.length());
// It is needed to set a zero-margin span on for the text under the image to prevent the space on the right!
s3.setSpan(new MyLeadingMarginSpan2(0, 0), 0, s3.length(), 0);
messageView.setText(TextUtils.concat(s1, s2, s3));
}
// remove the GlobalLayoutListener
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
messageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
messageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
If you need to wrap text around an image, use this library FlowTextView.
The library performs well, and it can be used with a couple lines. However, it does not support screen pixel size for fonts. I found a workaround with this answer, so that you can convert pixel size to sp.
I hope this helps anyone and you don't waste as much time as me using the question from my original post.
I'm just starting out with custom views. I'd like to create a view and have it repeat n times down a layout.
With the following code, the custom view is not repeating. What's more, if I insert it before the label and the button, then they are not rendered. However, if I insert the custom view after the other two controls, then they are shown.
I've played around with onLayout etc however have had no success.
Can someone please point me in the right direction?
Cheers.
public class ThreeRects : View
{
Paint _boxPaint;
Paint _boxPaint2;
Paint _boxPaint3;
public ThreeRects (Context context): base(context)
{
this._boxPaint = new Paint (PaintFlags.AntiAlias);
this._boxPaint.SetStyle (Paint.Style.Fill);
_boxPaint.Color = Color.Aqua;
this._boxPaint2 = new Paint (PaintFlags.AntiAlias);
this._boxPaint2.SetStyle (Paint.Style.Fill);
_boxPaint2.Color = Color.Red;
this._boxPaint3 = new Paint (PaintFlags.AntiAlias);
this._boxPaint3.SetStyle (Paint.Style.Fill);
_boxPaint3.Color = Color.Blue;
}
protected override void OnDraw (Android.Graphics.Canvas canvas)
{
var rect = new RectF (0, 0, 50, 50);
var rect2 = new RectF (50, 50, 100, 100);
var rect3 = new RectF (100, 100, 150, 150);
canvas.DrawRect (rect, this._boxPaint);
canvas.DrawRect (rect2, this._boxPaint2);
canvas.DrawRect (rect3, this._boxPaint3);
base.OnDraw (canvas);
}
}
[Activity (Label = "FertPinAndroid", MainLauncher = true)]
public class MainActivity : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate(bundle);
var layout = new LinearLayout (this);
layout.Orientation = Orientation.Vertical;
var aLabel = new TextView (this);
aLabel.Text = "Hello World";
var aButton = new Button (this);
aButton.Text = "Say Hello";
aButton.Click += (sender, e) => {
aLabel.Text = "Hello from the button";
};
layout.AddView (new ThreeRects (this));
layout.AddView (aLabel);
layout.AddView (aButton);
layout.AddView (new ThreeRects (this));
layout.AddView (new ThreeRects (this));
SetContentView (layout);
}
}
You didn't specify layout params for the LinearLayout or the sub-views. I would put layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT); and for each of the ThreeRects instances use the same. You also need to override onMeasure(int,int) in the ThreeRects class so Android can figure out how to include it in a layout. Maybe they are getting drawn on top of each other because you didn't override that method.