I am trying to change the tinting color of an EditText View programmatically during runtime. Basically i want to change what you would usually apply as ?attr/colorControlNormal like in the default background drawable.
Changing the background tint does not correctly apply by just setting a new ColorsStateList with one color:
editText.setBackgroundTintList( ColorStateList.valueOf( color ) );
For one the result is applied to all EditText although the tint list is applied and internally mutates the drawable. Also the alpha as specified in the default background 1 is visible at the beginning.
Here is the outcome of setting the tint color on just the first EditText:
So my question would be: How can I properly apply the tint programmatically to an EditText?
This works for me:
editText.getBackground().setColorFilter(getResources().getColor(R.color.your_color),
PorterDuff.Mode.SRC_ATOP);
Source: Changing EditText bottom line color with appcompat v7
With the newly introduced android.support.v4.graphics.drawable.DrawableCompat#setTint setting the color is now possible.
Try to create a custom EditText and add this.setBackgroundTintList( ColorStateList.valueOf( color ) ); into constructor.
setColorFilter not working for me. I used:
Drawable wrappedDrawable = DrawableCompat.wrap(mView.getBackground());
DrawableCompat.setTint(wrappedDrawable, getResources().getColor(R.color.red));
mView.setBackgroundDrawable(wrappedDrawable);
or
DrawableCompat.setTint(mView.getBackground(), ContextCompat.getColor(this, R.color.red));
Let's try.
for Kotlin
editText.backgroundTintList = ColorStateList.valueOf(R.color.colorLightGray )
I wrote a small component to achieve this behavior.
Few important notes:
Old school setColorFilter method is used
To make tint work, first switch focus to other view, then tint EditText background drawable
Usage
ErrorLabelLayout layoutPassError = (ErrorLabelLayout) findViewById(R.id.layoutPasswordError)
layoutPassError.setError("Password_is_wrong");
// when you want to clear error e.g. in on text changed method
layoutPassError.clearError();
XML
<com.view.material.ErrorLabelLayout
android:id="#+id/layoutPasswordError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false">
<EditText
android:id="#+id/editPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="Enter your password"/>
</com.view.material.ErrorLabelLayout>
Source
public class ErrorLabelLayout extends LinearLayout implements ViewGroup.OnHierarchyChangeListener {
private static final int ERROR_LABEL_TEXT_SIZE = 12;
private static final int ERROR_LABEL_PADDING = 4;
private TextView mErrorLabel;
private Drawable mDrawable;
private int mErrorColor;
public ErrorLabelLayout(Context context) {
super(context);
initView();
}
public ErrorLabelLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public ErrorLabelLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
setOnHierarchyChangeListener(this);
setOrientation(VERTICAL);
mErrorColor = Color.parseColor("#D32F2F");
initErrorLabel();
}
private void initErrorLabel() {
mErrorLabel = new TextView(getContext());
mErrorLabel.setFocusable(true);
mErrorLabel.setFocusableInTouchMode(true);
mErrorLabel.setTextSize(ERROR_LABEL_TEXT_SIZE);
mErrorLabel.setTextColor(mErrorColor);
mErrorLabel.setPadding(dipsToPix(ERROR_LABEL_PADDING), 0, dipsToPix(ERROR_LABEL_PADDING), 0);
}
public void setErrorColor(int color) {
mErrorColor = color;
mErrorLabel.setTextColor(mErrorColor);
}
public void clearError() {
mErrorLabel.setVisibility(INVISIBLE);
mDrawable.clearColorFilter();
}
public void setError(String text) {
mErrorLabel.setVisibility(VISIBLE);
mErrorLabel.setText(text);
// changing focus from EditText to error label, necessary for Android L only
// EditText background Drawable is not tinted, until EditText remains focus
mErrorLabel.requestFocus();
// tint drawable
mDrawable.setColorFilter(mErrorColor, PorterDuff.Mode.SRC_ATOP);
}
#Override
public void onChildViewAdded(View parent, View child) {
int childCount = getChildCount();
if (childCount == 1) {
mDrawable = getChildAt(0).getBackground();
addView(mErrorLabel);
}
}
#Override
public void onChildViewRemoved(View parent, View child) {
}
private int dipsToPix(float dps) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dps, getResources().getDisplayMetrics());
}
}
Tested on API 16 / 21 with com.android.support:appcompat-v7:22.1.1 library.
Related
I'm using custom checkbox for rtl support using rightDrawable property.
public class SRCheckBox extends AppCompatCheckBox {
public SRCheckBox(Context context) {
super(context);
init(context);
}
private void init(Context context) {
if (isRTL()) {
this.setButtonDrawable(null);
int[] attrs = {android.R.attr.listChoiceIndicatorMultiple};
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs);
Drawable rightDrawable = ta.getDrawable(0);
this.setCompoundDrawablesWithIntrinsicBounds(null, null, rightDrawable, null);
}
}
}
but here is the problem that I'm facing with: please looke at this gif
As you can see touch animation is affecting on left side (on text) instead of
animating on the checkbox itself.
I've also tried in XML:
<CheckBox
android:id="#+id/fastDecodeCB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="#null" // this is causing the problem
android:drawableRight="?android:attr/listChoiceIndicatorMultiple" />
but it looks the same. any suggestions?
You are setting the checkbox button to null effectively removing it and setting a right drawable. The right drawable responds to the clicks, but the checkbox doesn't really know that the drawable is the button (you told it there is no button), so it just does what you see.
Try the following for the init method in your custom view.
private void init(Context context) {
if (isRTL()) {
// This will flip the text and the button drawable. This could also be set in XML.
setLayoutDirection(LAYOUT_DIRECTION_RTL);
int[] attrs = {android.R.attr.listChoiceIndicatorMultiple};
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs);
Drawable rightDrawable = ta.getDrawable(0);
this.setButtonDrawable(rightDrawable);
ta.recycle(); // Remember to do this.
}
}
I am trying to use Autosizing TextViews in a RecyclerView, but when I scroll a few times the text gets so small that it's obviously not working properly.
Example of my TextView:
<android.support.v7.widget.AppCompatTextView
android:id="#+id/textview_unit_title"
android:layout_width="#dimen/tile_image_size"
android:layout_height="wrap_content"
android:maxLines="2"
android:textSize="#dimen/medium_size"
android:textColor="#color/color_text"
android:paddingTop="#dimen/padding_title"
android:layout_marginRight="2dp"
android:layout_marginEnd="2dp"
app:autoSizeMaxTextSize="#dimen/style_medium"
app:autoSizeTextType="uniform"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="#id/imageview_unit_icon"
app:layout_constraintTop_toTopOf="parent"/>
Should I update this scaling somewhere else programmatically or is there another solution?
The issue I've seen with this is that setting your view height to be wrap_content allows the text size to get smaller, but the text will never get bigger again. This is why the documentation recommends to not use wrap_content for the view size. However, I've found that if you turn off the auto-resizing, set the text size to whatever the max is, then re-enable auto-resizing, the text size resets to the largest size and scales down as necessary.
So my view in XML would look like:
<android.support.v7.widget.AppCompatTextView
android:id="#+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAllCaps="true"
android:textColor="#android:color/white"
android:textSize="42sp"
app:autoSizeMinTextSize="26dp"
app:autoSizeMaxTextSize="42dp"
app:autoSizeTextType="none"/>
Then in my ViewHolder when I bind my text to the view:
TextView title = view.findViewById(R.id.text_title);
String titleValue = "Some Title Value";
// Turn off auto-sizing text.
TextViewCompat.setAutoSizeTextTypeWithDefaults(title,
TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
// Bump text size back up to the max value.
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 42);
// Set your text as normal.
title.setText(titleValue);
// Post a runnable to re-enable auto-sizing text so that it occurs
// after the view is laid out and measured at max text size.
title.post(new Runnable() {
#Override
public void run() {
TextViewCompat
.setAutoSizeTextTypeUniformWithConfiguration(title,
26, 42, 1, TypedValue.COMPLEX_UNIT_DIP);
}
});
Autosizing TextViews
Android 8.0 (API level 26) allows you to instruct a TextView to let the text size expand or contract automatically to fill its layout based on the TextView's characteristics and boundaries.
Note: If you set autosizing in an XML file, it is not recommended to
use the value "wrap_content" for the layout_width or layout_height
attributes of a TextView. It may produce unexpected results.
You should bound height
android:layout_height="30dp"
Pavel Haluza's answer's approach was great. However, it didn't work, probably because he missed a line setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);.
Here is my updated version:
public class MyTextView extends AppCompatTextView {
private int minTextSize;
private int maxTextSize;
private int granularity;
public MyTextView(Context context) {
super(context);
init();
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
granularity = Math.max(1, TextViewCompat.getAutoSizeStepGranularity(this));
}
#Override
public void setText(CharSequence text, BufferType type) {
// this method is called on every setText
disableAutoSizing();
setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
super.setText(text, type);
post(this::enableAutoSizing); // enable after the view is laid out and measured at max text size
}
private void disableAutoSizing() {
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
}
private void enableAutoSizing() {
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this,
minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}}
I packaged Michael Celey's answer into a class. The parameters app:autoSizeMinTextSize, app:autoSizeMaxTextSize, app:autoSizeTextType are taken from xml.
public class AutosizingTextView extends AppCompatTextView {
private int minTextSize;
private int maxTextSize;
private int granularity;
public AutosizingTextView(Context context) {
super(context);
init();
}
public AutosizingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AutosizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
granularity = TextViewCompat.getAutoSizeStepGranularity(this);
}
#Override
public void setText(CharSequence text, BufferType type) {
// this method is called on every setText
disableAutosizing();
super.setText(text, type);
post(this::enableAutosizing); // enable after the view is laid out and measured at max text size
}
private void disableAutosizing() {
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
}
private void enableAutosizing() {
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this,
minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}
}```
the above solutions didn't work for me so here's mine
public class MyTextView extends AppCompatTextView {
...
#Override
public final void setText(CharSequence text, BufferType type) {
// work around stupid auto size text not *growing* the font size we re binding in a RecyclerView if previous bind caused a small font
int minTextSize = 0, maxTextSize = 0, granularity = 0;
boolean doHack = TextViewCompat.getAutoSizeTextType(this) != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
if (doHack) {
minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
if (minTextSize <= 0 || maxTextSize <= minTextSize) { // better than validateAndSetAutoSizeTextTypeUniformConfiguration crashing
if (BuildConfig.DEBUG)
throw new AssertionError("fix ya layout");
doHack = false;
} else {
granularity = TextViewCompat.getAutoSizeStepGranularity(this);
if (granularity < 0)
granularity = 1; // need this else setAutoSizeTextTypeUniformWithConfiguration barfs. TextView.UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = 1.
// make the TextView have 0 size so setAutoSizeTextTypeUniformWithConfiguration won't do calculations until after a layout pass using maxSize
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY));
setRight(getLeft());
setBottom(getTop());
requestLayout();
}
}
super.setText(text, type);
if (doHack)
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this, minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}
...
}
Just .setText("") before resetting the text size you want. That ensures that you are not setting the textsize and then immediately autoresizing using the previous text value in the TextView. Like this:
TextView wordWordTextView = getView().findViewById(R.id.wordWordTextView);
wordWordTextView.setAlpha(0.0f);
wordWordTextView.setText("");
wordWordTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 50);
wordWordTextView.setText(wordStr);
wordWordTextView.animate().alpha(1.0f).setDuration(250);
I only just set android:maxLines="1" in xml file, then code in bindViewHolder
TextViewCompat.setAutoSizeTextTypeWithDefaults(binding.tvResultExplain, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
binding.tvResultExplain.setText("");
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(binding.tvResultExplain, 12,
16, 1, TypedValue.COMPLEX_UNIT_SP);
binding.tvResultExplain.setText(item.getStatusExplain());
It works for me, maybe it can resolve your situation as well.
I am creating several textviews that all use the same style. I am attempting to use a SeekBar to update the textsize within the Style so it applies to all textviews with a minimal amount of code. I know I can use a SeekBar to set the textsize of the textviews individually but that seems like a lot of work. The problem is that everywhere I look all I find is that you cannot change the style. Is there any other work around besides doing code like below:
Define my textviews
TextView tv1 = (TextView)findViewById(R.id.tv1);
TextView tv2 = (TextView)findViewById(R.id.tv2);
TextView tv3 = (TextView)findViewById(R.id.tv3);
Inside my SeekBar
progress = seekBarProgress;
if(progress == 0)
{
tv1.setTextSize(12);
tv2.setTextSize(12);
tv3.setTextSize(12);
}
if(progress == 1)
{
tv1.setTextSize(14);
tv2.setTextSize(14);
tv3.setTextSize(14);
}
Etc etc..
I would like to be able to change one attribute of a custom style. I cannot change it all together to a different custom style because I am going to do SeekBars for Text size, text color, background color, etc. If I did custom styles for each one there would be TONS.
Since I will have a lot of textviews doing this method seems illogical. Is there a better way? Thanks.
GOT THE ANSWER!
Instead of changing the style I retrieve the child and then the child of that child and change it accordingly like below.
LinearLayout masterLayout = (LinearLayout)findViewById(R.id.masterLayout);
int childCount = masterLayout.getChildCount();
for(int i = 0; i < childCount; i++)
{
LinearLayout innerChild = ((LinearLayout)masterLayout.getChildAt(i));
int childOfChildCount = innerChild.getChildCount();
for(int x = 0; x < childOfChildCount; x++)
{
((TextView)innerChild.getChildAt(x)).setTextSize(30);
}
}
What about group these TextView in only one Layout? Then change it programmatically.
In my example I group all of TextViews in only one LinearLayout.
LinearLayout ll = (LinearLayout) findViewById(R.id.layout);
int childCount = ll.getChildCount();
for (int i=0; i<childCount; i++){
((TextView)ll.getChildAt(i)).setTextSize(20);
}
Be sure that you only have TextViews in your layout.
I know that you have already implemented and accepted a solution, however, I have been thinking about this for a while for myself, and have come up with an alternative, more generic solution which may be of use. This involves four elements
Creating an interface for the style changed events
Creating a handler for the style changed events
Extending TextView to have one or more style changed events
Triggering the style change events
Although this is more code it has the advantages of being independent of layouts, and of the view classes (ie the same handler can be used for different View Classes if you also wanted to change the font size of Buttons, EditTexts etc).
The example below just implements a text size change, but the same technique could be used to implement any other style changes.
The Interface
public interface StyleChange {
void onTextSizeChanged(float size);
}
The Handler
public class TextStyleHandler {
private static TextStyleHandler instance;
private LinkedList<StyleChange> listeners = new LinkedList<>();
public static TextStyleHandler getInstance() {
if (instance == null) instance = new TextStyleHandler();
return instance;
}
public void register(StyleChange item) {
listeners.add(item);
}
public void unregister(StyleChange item) {
listeners.remove(item);
}
public void setTextSize(float f) {
for (StyleChange listener:listeners)
listener.onTextSizeChanged(f);
}
}
The Extended TextView
public class StyledTextView extends TextView implements StyleChange {
public StyledTextView(Context cx) {
super(cx);
init();
}
public StyledTextView(Context cx, AttributeSet attrs) {
super(cx, attrs);
init()
}
public StyledTextView(Context cx, AttributeSet attrs, int defStyle) {
super(cx, attrs, defStyle);
init();
}
private void init() {
// Any other setup here (eg setting the default size
// or getting current value from shared preferences)
TextStyleHandler.getInstance().register(this);
}
public void onTextSizeChanged(float size) {
setTextSize(size);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
TextStyleHandler.getInstance().unregister(this);
}
}
Triggering the style change event
This can be done from your activity, and will change the style of all registered views
TextStyleHandler.getInstance().setTextSize(size);
I found how to change the opacity of a View, but I need to actually darken a View. My best idea is to put a transparent black rectangle over it and then slowly increase the opacity of the rectangle.
Do you know a nicer way to do it?
public class Page07AnimationView extends ParentPageAnimationView {
private final String TAG = this.getClass().getSimpleName();
private ImageView overlay;
private int mAlpha = 0;
public Page07AnimationView(Context context) {
super(context);
}
public Page07AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void init()
{
overlay = new ImageView(mContext);
overlay.setImageResource(R.drawable.black_background);
overlay.setAlpha(0);
overlay.setWillNotDraw(false);
// make the PageAniSurfaceView focusable so it can handle events
setFocusable(true);
}
protected void draw_bitmaps(Canvas canvas)
{
overlay.draw(canvas);
update_bitmaps();
invalidate();
}
public void update_bitmaps()
{
if(mAlpha < 250)
{
mAlpha += 10;
overlay.setAlpha(mAlpha);
}
}
}
The code above isn't doing what I had hoped. Page07AnimationView is added to a FrameLayout over the view I need to darken. R.drawable.black_background points to a 787px x 492px black png image.
I added overlay.setWillNotDraw(false); but it didn't help.
I changed the first setAlpha(0) to setAlpha(255) but that didn't help.
I removed the setAlpha() calls altogether, but it didn't help.
This basic technique of adding a PageNNAnimationView has been working to draw Bitmaps, but not to draw ImageView overlay. (I would use Bitmaps, but they don't seem to have an alpha component.)
Edit2: this is the parent of the class above:
public class ParentPageAnimationView extends View {
private final String TAG = this.getClass().getSimpleName();
protected Context mContext;
public ParentPageAnimationView(Context context) {
super(context);
mContext = context;
init();
}
public ParentPageAnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
protected void init()
{
}
protected void draw_bitmaps(Canvas canvas)
{
// will be overridden by child classes
}
#Override
protected void onDraw(Canvas canvas) {
if(this.getVisibility() == View.VISIBLE)
{
if(canvas != null)
{
draw_bitmaps(canvas);
}
}
}
public void update_bitmaps()
{
// will be overridden by child classes
}
public void elementStarted(PageElement _pageElement) {
// Nothing in parent class
}
public void elementFinished(PageElement mElement) {
// Nothing in parent class
}
}
In case of an ImageView, here's one way to achieve it:
imageView.setColorFilter(Color.rgb(123, 123, 123), android.graphics.PorterDuff.Mode.MULTIPLY);
I would rather do it in the opposite way - put a dark rectangle behind the view and set the view's opacity. This saves painting the rectangle when the view is 100% opaque.
I would do something like this:
view.getBackground().setColorFilter(color, PorterDuff.Mode.DARKEN);
Use black color with some alpha like 0x7f000000 for a typical darkening.
It's more concise and you can also darken the View with animation or scrolling event for example. Just set Color.argb(alpha, 0, 0, 0) as the color and animate alpha, or change it based on the scrolling offset.
This is how I ended up doing it. The key was to use a Paint with its alpha set to whatever I wanted.
public class Page07AnimationView extends ParentPageAnimationView {
private final String TAG = this.getClass().getSimpleName();
private Bitmap bitmap;
private BitmapDrawable drawable;
private ImageView overlay;
private int which = -1;
private long last_time;
private Page07State state;
private int mAlpha;
private int maxAlpha;
private Paint mPaint;
private int _alpha_step;
private int minAlpha;
public enum Page07State {
WAITING, DARKENING, DARKENED
}
public Page07AnimationView(Context context) {
super(context);
}
public Page07AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void init()
{
minAlpha = 0;
mAlpha = minAlpha;
_alpha_step = 5;
maxAlpha = 255;
mPaint = new Paint();
mPaint.setAlpha(minAlpha);
state = Page07State.WAITING;
overlay = new ImageView(mContext);
overlay.setImageResource(R.drawable.black_background);
drawable = (BitmapDrawable) overlay.getDrawable();
bitmap = drawable.getBitmap();
last_time = 0;
}
protected void draw_bitmaps(Canvas canvas)
{
if(state != Page07State.WAITING)
{
DebugLog.d(TAG, "drawing " + Integer.toString(which));
canvas.drawBitmap(bitmap, 0, 0, mPaint);
}
update_bitmaps();
invalidate();
}
public void update_bitmaps()
{
if(state == Page07State.DARKENING)
{
if(mAlpha < maxAlpha)
{
if(System.currentTimeMillis() > last_time + 12)
{
last_time = System.currentTimeMillis();
mAlpha += _alpha_step;
mPaint.setAlpha(mAlpha);
}
}
else
{
state = Page07State.DARKENED;
}
}
}
public void runAnimation()
{
state = Page07State.DARKENING;
}
}
Adding to android developer's answer:
imageView.setColorFilter(Color.rgb(123, 123, 123), android.graphics.PorterDuff.Mode.MULTIPLY);
you can setColorFilter on any view like this:
GradientDrawable gd = (GradientDrawable) textView.getBackground();
gd.setColor(color); //you can also set BG color to a textview like this
gd.setColorFilter(Color.rgb(123, 123, 123), android.graphics.PorterDuff.Mode.MULTIPLY);
you could try using the Alpha animation like this (perhaps on the rectangle):
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(350);
That would cause the rectangle to gradually become opaque over 350 seconds...
Android actually exposes a drawable which can be used to darken views. You can easily attach it to any view with an Overlay.
Here are two extension functions which can be used to darken any view.
fun View.darken() {
val darkOverlay = ResourcesCompat.getDrawable(
resources,
android.R.drawable.screen_background_dark_transparent,
context.theme
)!!.mutate() // We mutate the drawable so we can later implement a fade in/out animation and animate the Drawable's alpha property. Since Drawables share their state we need to mutate otherwise we would impact all instances of this drawable
darkOverlay.setBounds(0, 0, width, height)
setTag(R.id.dark_overlay, darkOverlay)
overlay.add(darkOverlay)
}
fun View.lighten() {
(getTag(R.id.dark_overlay) as? Drawable)?.let {
overlay.remove(it)
setTag(R.id.dark_overlay, null)
}
}
Make sure you add the id to ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="dark_overlay" type="id" />
</resources>
And if you're darkening your application's root layout and would like to darken the NavigationBar as well, you might need to add the the following to your theme in styles.xml
<style name="BaseTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- required for api 29 otherwise the system will set a white background color to the NavigationBar to ensure the buttons are visible -->
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
</style>
You should check iPaulPro's answer in this question. You will need to extend ImageView and override the onDraw() method.
Depending on what you are going to do, Alexandru Cristescu's answer is also valid but you should
call setFillAter(true) for the animation to persist after finished.
When I include the below XML to layout file, I can see the below image. If you see it, you could realize that the TextView has top and bottom space.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="E1"
android:background="#ff00ff00"/>
I wish to remove the space. How to remove it? What is it called?
If anyone has clue.. please let me know. Thanks in advance.
Try android:includeFontPadding="false" to see if it helps. In my experience that will help a little bit, but there's no way of reducing the TextView dimensions to the exact pixel-perfect text size.
The only alternative, which may or may not give better results, is to cheat a bit and hard-wire the dimensions to match the text size, e.g. "24sp" instead of "wrap_content" for the height.
I had the same problem. Attribute android:includeFontPadding="false" does not work for me. I've solved this problem in this way:
public class TextViewWithoutPaddings extends TextView {
private final Paint mPaint = new Paint();
private final Rect mBounds = new Rect();
public TextViewWithoutPaddings(Context context) {
super(context);
}
public TextViewWithoutPaddings(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TextViewWithoutPaddings(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(#NonNull Canvas canvas) {
final String text = calculateTextParams();
final int left = mBounds.left;
final int bottom = mBounds.bottom;
mBounds.offset(-mBounds.left, -mBounds.top);
mPaint.setAntiAlias(true);
mPaint.setColor(getCurrentTextColor());
canvas.drawText(text, -left, mBounds.bottom - bottom, mPaint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
calculateTextParams();
setMeasuredDimension(mBounds.width() + 1, -mBounds.top + 1);
}
private String calculateTextParams() {
final String text = getText().toString();
final int textLength = text.length();
mPaint.setTextSize(getTextSize());
mPaint.getTextBounds(text, 0, textLength, mBounds);
if (textLength == 0) {
mBounds.right = mBounds.left;
}
return text;
}
}
android:includeFontPadding="false" is pretty good but it does not get it precisely. sometimes you want border line accuracy so you can figure it out yourself by applying negative margins:
try setting your bottom and top margins to a negative value.
something like this:
android:layout_marginTop="-5dp"
android:layout_marginBottom="-5dp"
adjust the values accordingly.
This is the code that saved our day. It was adapted using mono C# code from maksimko:
public class TopAlignedTextView extends TextView {
public TopAlignedTextView(Context context) {
super(context);
}
/*This is where the magic happens*/
#Override
protected void onDraw(Canvas canvas){
float offset = getTextSize() - getLineHeight();
canvas.translate(0, offset);
super.onDraw(canvas);
}
}
Still had to play around with textView.setIncludeFontPadding(false) because we were aligning TextViews with different font sizes.
I faced the same problem.
Here's a good answer: How to align the text to top of TextView?
But code is little unfinished and don't support all font sizes. Change the line
int additionalPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getContext().getResources().getDisplayMetrics());
to
int additionalPadding = getTextSize() - getLineHeight();
Complete C# code (mono) removes top offset:
public class TextControl : TextView {
public TextControl (Context context) : base (context)
{
SetIncludeFontPadding (false);
Gravity = GravityFlags.Top;
}
protected override void OnDraw (Android.Graphics.Canvas canvas)
{
if (base.Layout == null)
return;
Paint.Color = new Android.Graphics.Color (CurrentTextColor);
Paint.DrawableState = GetDrawableState ();
canvas.Save ();
var offset = TextSize - LineHeight;
canvas.Translate (0, offset);
base.Layout.Draw (canvas);
canvas.Restore ();
}
}
Just wanted to add to DynamicMind's answer that the reason why you see spacing around your TextViews is padding in 9-patch backgrounds they use by default.
9-patch technology allows you to specify a content area which is, effectively, padding. That padding is used unless you set the view's padding explicitly. E.g., when you programmatically set a 9-patch background to a view which had paddings set, they are overridden. And vise-versa, if you set paddings they override what was set by 9-patch background.
Unfortunately, in the XML layout it's not possible to determine the order of these operations. I think just removing the background from your TextViews would help:
android:background="#null"
public class TopAlignedTextView extends TextView {
public TopAlignedTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TopAlignedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
setIncludeFontPadding(false); //remove the font padding
setGravity(getGravity() | Gravity.TOP);
}
#Override
protected void onDraw(Canvas canvas) {
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
//remove extra font padding
int yOffset = getHeight() - getBaseline();
canvas.translate(0, - yOffset / 2);
if (getLayout() != null) {
getLayout().draw(canvas);
}
canvas.restore();
}
}
Modified this answer a little bit to use kotlin class and extend AppCompatTextView, trimming vertical padding.
It allows setting android:fontFamily. Method calculateTextParams() moved from onDraw() for performance. Not tested for multiple lines of text:
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class NoPaddingTextView : AppCompatTextView
{
private val boundsRect = Rect()
private val textParams = calculateTextParams()
constructor(context : Context?)
: super(context)
constructor(context : Context?, attrs : AttributeSet?)
: super(context, attrs)
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int)
: super(context, attrs, defStyleAttr)
override fun onDraw(canvas : Canvas)
{
with(boundsRect) {
paint.isAntiAlias = true
paint.color = currentTextColor
canvas.drawText(textParams,
-left.toFloat(),
(-top - bottom).toFloat(),
paint)
}
}
override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
calculateTextParams()
setMeasuredDimension(boundsRect.width() + 1, -boundsRect.top + 1)
}
private fun calculateTextParams() : String
{
return text.toString()
.also {text ->
text.length.let {textLength ->
paint.textSize = textSize
paint.getTextBounds(text, 0, textLength, boundsRect)
if(textLength == 0) boundsRect.right = boundsRect.left
}
}
}
}
Have you defined a layout margin?
For example:
android:layout_marginTop="5dp"
Otherwise, if your text view is wrapped inside a LinearLayout or other container, then that cold have either padding or a margin too.
android:background="#android:drawable/editbox_background"
use it according to you change it that you want editbox_background.
because android provide some build in background like above code choose according to your requirement.
May be it is help full to you.
Inside a LinearLayout the default padding might be an issue. Try setting it to 0dp. It worked for me.
The answer of TopAlignedTextView code:TopAlignedTextView#GitHub
use it by layout:
<com.github.captain_miao.view.TopAlignedTextView
android:id="#+id/text_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:text="#string/text_demo_a"
/>
My way for fixing this is pretty hacky, but I managed to get the text to sit where I wanted by setting the height of the text view as static and fiddling with it until it just barely fit the text. In my case, the font style I was using had a height of 64sp so I set the height of my textview to 50sp and it worked okay. I also had to set foreground_gravity to bottom.
android:includeFontPadding="false"