How to create simple layer drawable in button - android

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.

Related

Make gradient start further away from end of view with GradientDrawable

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

Android using icon in text of TextView

I´d like to use icons in the text of a TextView. (Not in front or in the end of it.) The basic idea is a short infobox, telling the user, that these fancy three lines up there are the menu. (Since it is not possible to write "Menu" below them, except I want to handle all the click events myself too. sigh)
So my text should be something like this:
Right now this is just an icon added at the end of the textview, but as you can clearly see, it looks very ugly, and only works with a fixed screen-size. In landscape mode the icon would be at the completely other side of the screen, than the text.
Therefore I am searching for some way to write icons inside of the text.
I was thinking about something like "Um das Menü zu öffnen, tippe auf #drawable/sandwich" in the string resources or similar. (Which obviously doesn´t work like that, sadly.)
Is this possible? Or, if not, is there maybe some secret trick to add a text to the sandwich icon in the action bar at the top, without creating my custom layout?
Around 50% of my users have issues realizing it is a menu, since they are not used to a lot of apps.
Yes, you can do it using SpannableString. See the below example:
val modifiedText = "your-text-here %icon%" // you can use resource string here
val span = SpannableString(modifiedText)
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.your_image, null)
drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
val image = ImageSpan(drawable, ImageSpan.BOTTOM)
val startIndex = modifiedText.indexOf("%icon%")
//Replace %icon% with drawable
span.setSpan(image, startIndex, startIndex + 6, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
yourTextView.setText(span)
You could use VerticalImageSpan which is an extension to ImageSpan.
And use the following TextView extension function takes in the index in the TextView that you want to place the drawable in; a drawable, the desired image width & height.
fun TextView.addDrawableAt(index: Int, #DrawableRes imgSrc: Int, imgWidth: Int, imgHeight: Int) {
val ssb = SpannableStringBuilder(this.text)
val drawable = ContextCompat.getDrawable(this.context, imgSrc) ?: return
drawable.mutate()
drawable.setBounds(
0, 0,
imgWidth,
imgHeight
)
ssb.setSpan(
VerticalImageSpan(drawable),
index - 1,
index,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
this.setText(ssb, TextView.BufferType.SPANNABLE)
}

Change position of icon drawable in TextView

In my case, I have a textView with a drawable icon at the start of it(android:drawableStart), when the text of textView is multi-line, the icon goes in the vertical center of textView, but I want the icon to be aligned to the top of the text, how can I do this?
I don't want use any other layouts for doing this
There is api to set the drawable, but first you should set the bounds .
You can use setCompoundDrawablesWithIntrinsicBounds to set the drawable icons.
textview.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(getApplicationContext(), R.drawable.icon), null, ContextCompat.getDrawable(getApplicationContext(), R.drawable.icon_two), null);
As according to the interface you can see the position clearly and use accordingly to your need.
public void setCompoundDrawablesWithIntrinsicBounds (int left,
int top,
int right,
int bottom)
It is something different but a quick workaround.
Just use a Checkbox instead of a TextView and use android:button to define your drawable:
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:button="#drawable/..."
android:gravity="top"
android:clickable="false"
android:text=".."/>
The approach I followed is to use SpannableString by setting a Span to the beginning of the string, being the Spain an ImageSpain
fun TextView.setDrawableAtStart(context: Context, textToSet: String?, #DrawableRes drawableRes: Int) {
if (textToSet.isNullOrBlank()) return
text = SpannableString(" $textToSet ").apply {
val drawable = ContextCompat.getDrawable(context, drawableRes) ?: return
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
setSpan(
ImageSpan(drawable, ImageSpan.ALIGN_BASELINE),
0, // Start position
1, // End position
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
I encourage you to read this interesting article about styling a text in Android.

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

ProgressBar inside MaterialButton

I was looking at material design website when I got to this page about circular progressbar and more precisely this video
I'd like to create a button like the one in the video but I don't see any documentation about this, and I don't know where to start
I've managed to simply use:
val progress = CircularProgressDrawable(context).apply {
setStyle(CircularProgressDrawable.DEFAULT)
// do use setBounds if you need to
start()
}
myMaterialButton.setCompoundDrawablesWithIntrinsicBounds(null, progress, null, null)
// Do your stuff
//replace the image by the one you want
myMaterialButton.setCompoundDrawablesWithIntrinsicBounds(null, myDrawable, null, null)
Reference: setCompoundDrawablesWithIntrinsicBounds
You could also use IndeterminateDrawable from material progress indicator. I see the CircularProgressDrawable animation breaks in API level 25
private val progressDrawableBackward by lazy {
val progressIndicatorSpec = CircularProgressIndicatorSpec(
context,
null,
0,
R.style.Widget_MaterialComponents_CircularProgressIndicator_ExtraSmall
)
progressIndicatorSpec.indicatorColors =
intArrayOf(progressColor)
IndeterminateDrawable.createCircularDrawable(context, progressIndicatorSpec).apply {
val size = (DRAWABLE_CENTER_RADIUS + DRAWABLE_STROKE_WIDTH ).toInt() * 2
setBounds(0, 0, size, size)
setVisible(true, true)
}
}

Categories

Resources