Make gradient start further away from end of view with GradientDrawable - android

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))
)

Related

Setting icon for TextInputLayout helper text and centering it to top

So I need to set up 2 things:
Icon for helper text in TextInputLayout
Center it to TOP, since helper text might be multiline
It might look something like this:
Is there easy of doing working around this, without making my own TextInputLayout?
Solving this melts down to following 2 possible solutions:
First is to change the Rect of the Drawable itself by setting the bounds (coordinates X and Y), drawback of this solution is whenever layout is redrawn (orientation change etc.), this has to be done again.
findViewById<AppCompatTextView>(R.id.textinput_helper_text)?.run {
compoundDrawables.first()?.run {
setBounds(
bounds.left,
bounds.top - this#with.height.div(2),
bounds.right,
bounds.bottom - this#with.height.div(2)
)
bottom = this#with.height.div(2)
}
}
Second solution is to add new ImageView in front of the helper TextView and add padding to it, so it's pushed up.
It's not a one liner, but it can be formed into easy to use method as such:
fun TextInputLayout.setStartHelperTextIcon(
#DrawableRes drawableRes: Int,
paddingPx: Int
) {
with(findViewById<AppCompatTextView>(R.id.textinput_helper_text)) {
(this?.parent?.parent as? LinearLayout)?.run {
if(findViewById<ImageView>(R.id.text_input_layout_helper_icon) == null) {
addView(
ImageView(context).apply {
id = R.id.text_input_layout_helper_icon
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding(
paddingPx,
0,
paddingPx,
paddingPx + this#with.height.div(2)
)
},
0
)
}
}
}
}

How to apply rounded corner to a view based on its width in Android Kotlin

I am working on a custom Progress bar as photos below:
Basically, I created a drawable xml background file:
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid
android:color="#color/jungleGreen"/>
<corners
android:bottomLeftRadius="40dp"
android:topLeftRadius="40dp"/>
</shape>
Then I applied it to the view that I am using:
<View
android:id="#+id/progress_bar_placeholder_view"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginEnd="45dp"
android:background="#drawable/background_filled_patronage_progressbar"
app:layout_constraintTop_toTopOf="parent"/>
It is totally fine and I can achieve scenario 1 and 2, but when the bar is getting closer to the end, how can I programmatically set the rounded corner for the top right and the bottom right part of the view until it looks like in photo 3?
Thanks.
try this
public static void customView(View v, int backgroundColor, int borderColor)
{
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.RECTANGLE);
shape.setCornerRadii(new float[] { 8, 8, 8, 8, 0, 0, 0, 0 });
shape.setColor(backgroundColor);
shape.setStroke(3, borderColor);
v.setBackground(shape);
}
Source : How to create android shape background programmatically?
Madhav's solution works for me, so basically I will need to pass in the width of the view, then calculate the corner radius number and put it into gradientDrawable as below:
private fun setupGraphBackground(view: View, graphWidth: Int) {
val gradientDrawable = GradientDrawable()
gradientDrawable.shape = GradientDrawable.RECTANGLE
gradientDrawable.setColor(resources.getColor(R.color.jungleGreen))
gradientDrawable.setStroke(0, null)
gradientDrawable.cornerRadii = floatArrayOf(45f, 45f,
graphWidth * calculationRules, graphWidth * calculationRules,
graphWidth * calculationRules, graphWidth * calculationRules,
45f, 45f)
view.background = gradientDrawable
}
Where as calculationRules is the rule I want so that I can get the right radius of the topRight and bottomRight corner.

How to create simple layer drawable in button

I am trying to better understand how layer drawables work within a buttons drawable(s).
I am trying to draw 2 simple colored boxes, one without insets so that it fills the entire button drawable area. And one with some inset.
ColorDrawable background1 = new ColorDrawable(Color.BLUE);
ColorDrawable background2 = new ColorDrawable(Color.GREEN);
Drawable[] drawables = new Drawable[] {
background1,
background2
};
LayerDrawable ld = new LayerDrawable(drawables);
ld.setLayerInset(0, 0, 0, 0, 0 ); // no inset on white box
ld.setLayerInset(1, 8, 8, 8, 8 ); // 8 inset on all side on green box
// set the left drawable on the button
button.setCompoundDrawablesWithIntrinsicBounds(ld, null, null, null);
However that doesn't work at all. The first problem is that the boxes are not filling any area. Is that because a buttons drawables(s) don't have a predefined size? If that is the case I tried to set the bound manually on the boxes, but didn't have much luck either.
Can anyone help me understand what I am doing wrong?
Create a Specific Design in Drawable and call in background button in xml
The problem right now with ColorDrawable is that getIntrinsicWidth()/getIntrinsicHeight() that determine the bounds of the drawable are by default -1, thus it doesn't render on the screen. What would help in your case is to extend ColorDrawable and override the height and width from the constructor.
Here's a sample:
class CustomDrawable(color: Int, val h: Int, val w: Int): ColorDrawable(color) {
override fun getIntrinsicHeight(): Int {
return h
}
override fun getIntrinsicWidth(): Int {
return w
}
}
and then you can instantiate this instead of ColorDrawable like so
val background1 = CustomDrawable(Color.BLUE, dimensionInPx , dimensionInPx)
val background2 = CustomDrawable(Color.GREEN, dimensionInPx, dimensionInPx)
be sure to convert dp to px before passing these values
Hope this helps.

Change background colour as gradient

I'm using Andengine to create and android app, and I'm wondering if there is any possible way to change the background colour in a gradient fashion.
I know you can create a gradient in Andengine and thats from one part of the screen to another I what each colour in the gradient to fill the screen as it changes over time.
If you are using the anchor center branch of andengine then you can just import the Gradient Class from org.andengine.entity.primitive.Gradient and set it's height and width to the camera width and height as shown:
final int CameraHeight = 480;
final int CameraWidth = 480;
int directionX = 1;
int directionY = 1;
Gradient g = new Gradient(0,0,CameraWidth,CameraHeight,
this.getVertexBufferObjectManager());
g.setGradient(yourFirstColor,yourSecondColor,directionX,directionY);
And then all you must do is attach it to your scene as the first child scene.attachChild(g);
Then to make it change over time you can register an update handler with the scene that changes the colors or changes the direction like:
scene.registerUpdateHandler(new IUpdateHandler(){
#Override
onUpdate(float pSeconds){
//Change the color or the direction of the gradient
}
});
or you could register an entity modifier like ColorModifier
ColorModifier cm = new ColorModifier(5, Color.BLUE, Color.GREEN);
g.registerEntityModifier(cm);

Text with gradient in Android

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

Categories

Resources