I would like to set a background drawable (or resource) on a TextView, not taking into account its compound drawable width (and paddings).
Getting the width of the compound (left one to be more precise), and its paddings should not be a problem, but the setting of the background on the width of the textview minus the width of the compound drawable (described above).
Should you have any advice on doing that, please let me know.
Here's the needed result:
PS. I thought about having a horizontal LinearLayout with an ImageView and TextView as its children, and having the background set on the textview only, but I am interested in having the same result with less Views (in this case, exactly one), if it is possible.
You could use a LayerDrawable which supports insets, for example:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:left="dimension" android:right="dimension">
<shape android:shape="rectangle">
<solid android:color="color" />
</shape>
</item>
</layer-list>
If you want to change your Drawable dynamically you're better off writing your own Drawable class. The following DividerDrawable for instance draws a line to a given padding onto a white background:
public class DividerDrawable extends Drawable {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mDensity;
private int mPaddingLeft = 0;
public DividerDrawable(Context context) {
mPaint.setColor(Color.BLACK);
mDensity = context.getResources().getDisplayMetrics().density;
}
#Override
public void draw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
canvas.drawColor(Color.WHITE);
canvas.drawRect(mPaddingLeft, height - mDensity, width, height, mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public void setPaddingLeft(int paddingLeft) {
if (mPaddingLeft != paddingLeft) {
mPaddingLeft = paddingLeft;
invalidateSelf();
}
}
}
To set the left padding based on your left CompoundDrawable you could do something like this:
private void setBackground(TextView textView, DividerDrawable background) {
Drawable drawableLeft = textView.getCompoundDrawables()[0];
int paddingLeft = drawableLeft != null ?
textView.getPaddingLeft() + drawableLeft.getIntrinsicWidth() + textView.getCompoundDrawablePadding() :
textView.getPaddingLeft();
background.setPaddingLeft(paddingLeft);
textView.setBackground(background);
}
To make good use of all this call it like so:
DividerDrawable dividerDrawable = new DividerDrawable(this);
TextView textView = (TextView) findViewById(R.id.text);
setBackground(textView, dividerDrawable);
Related
I am trying to figure out how to change the solid color and the stroke color based on the spinner selection.
spin_shape drawable
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#97233F" />
<stroke android:color="#FFC20E"
android:width="#dimen/button_border" />
</shape>
Button with the drawable as a background
<Button
android:id="#+id/team_a_td"
android:background="#drawable/spin_shape"
android:onClick="addSixForTeamA" />
Array resource xml file
<array name="color_array">
<item>#ff0000</item>
<item>#e9b300</item>
<item>#db9fd5</item>
</array>
<array name="stroke_color_array">
<item>#2988BC</item>
<item>#660006</item>
<item>#0C6674</item>
</array>
Here's what I used to change an image view based on the spinner selection. I want to see if there is something similar I can do to change the shape color and stroke color.
String[] listOfObjects = getResources().getStringArray(R.array.teams_array);
TypedArray images = getResources().obtainTypedArray(R.array.team_image);
TypedArray team_colors = getResources().obtainTypedArray(R.array.color_array);
TypedArray secondary_colors = getResources().obtainTypedArray(R.array.stroke_color_array);
Button team_a_td_color = (Button)findViewById(R.id.team_a_td);
final Spinner spinnerA = (Spinner)findViewById(R.id.team_a_spinner);
ArrayAdapter<String> spinnerAdapterA = new ArrayAdapter<String>(this,R.layout.spinner_item, listOfObjects);
spinnerAdapterA.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerA.setAdapter(spinnerAdapterA);
spinnerA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
itemImageA.setImageResource(images.getResourceId(spinnerA.getSelectedItemPosition(), -1));
}
I can see 2 ways to do that, one is create a custom Drawable class, the other is to use different themes.
Method 1 - custom class
public class MyJavaDrawable extends Drawable {
private final int fillColor;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public MyJavaDrawable(int fillColor, int strokeColor, float strokeWidth){
this.fillColor = fillColor;
paint.setColor(strokeColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
}
#Override
public void draw(#NonNull Canvas canvas) {
canvas.drawColor(fillColor);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
#Override
public int getOpacity() {
return paint.getAlpha();
}
}
// ...
MyJavaDrawable drawable = new MyJavaDrawable(fillColor, strokeColor, borderWidthPx);
imageView.setImageDrawable(drawable);
Since you're not using images but just drawing directly on canvas, you could easily instantiate many of these with different colors. Caveat n.1, I think that the value for strokeWidthPx should be doubled there because it is drawing exactly on the edges of the rectangle so half of that width would lay outside of the drawing area. Caveat n.2, the ImageView should have well-defined size, if you want to use wrap_content I think you should set intrinsic bound to the Drawable.
Method 2 - themes
In your styles.xml define 2 attributes for stroke and fill color, then as many styles as you need, for instance "red" and "blue" styles:
<attr name="SpinShapeFillColor" format="color" />
<attr name="SpinShapeStrokeColor" format="color" />
<style name="SpinShapeRed">
<item name="SpinShapeFillColor">#ffff0000</item>
<item name="SpinShapeStrokeColor">#ffff9900</item>
</style>
<style name="SpinShapeBlue">
<item name="SpinShapeFillColor">#ff0000ff</item>
<item name="SpinShapeStrokeColor">#ff9900ff</item>
</style>
Then change your spin_shape.xml to reference the attributes you've defined like so:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/SpinShapeFillColor" />
<stroke android:color="?attr/SpinShapeStrokeColor"
android:width="#dimen/button_border" />
</shape>
Then in your code you need to create a Resources.Theme for each style:
final Resources.Theme themeBlue = getResources().newTheme();
themeBlue.applyStyle(R.style.SpinShapeBlue, true);
final Resources.Theme themeRed = getResources().newTheme();
themeRed.applyStyle(R.style.SpinShapeRed, true);
And finally, you instantiate the Drawable with the selected Theme and set it to the ImageView:
Drawable drawable = getResources().getDrawable(R.drawable.spin_shape, themeBlue);
imageView.setImageDrawable(drawable);
A simple workaround is, you can create 2 drawables with 2 intended colors of yours and set drawable according to your condition e.g
Drawable drawable
if(needBlue){
drawable = getResources().getDrawable(R.drawable.spin_shape_blue);
}else{
drawable = getResources().getDrawable(R.drawable.spin_shape_orange);
}
imageView.setImageDrawable(drawable);
I am using that "hack".
I have read here in stackoverflow.
#Override
public void draw(Canvas canvas) {
for (int i = 0; i < 20; i++) {
super.draw(canvas);
}
}
But my border still smoothie,I wanna put a large and solid border on all my TextView (I already have my component extend a textview).
I have a selector in text color when I click in this text the text color need to change.(It was already working,but I tried to apply another alternative using canvas,in this alternative,I lost this comportment).
This page solve your problem, you can custom the style:
How do I put a border around an Android textview?
You can set a shape drawable (a rectangle) as background for the view.
<TextView android:text="Some text" android:background="#drawable/back"/>
And rectangle drawable back.xml (put into res/drawable folder):
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="#ffffff" />
<stroke android:width="1dip" android:color="#4fa5d5"/>
</shape>
You can use #00000000 for the solid color to have a transparent background. You can also use padding to separate the text from the border. for more information see: http://developer.android.com/guide/topics/resources/drawable-resource.html
Your current solution (the hack) is working fine, you just have to tweak 2 parameters accordingly to get a better "solid" shadow effect.
Parameters
The 1st parameter is the shadow radius of the TextView. This parameter decides how "wide" the blur (shadow) effect will spread behind your letter.
The 2nd parameter is the repeat counter of the for loop that wraps around your TextView's onDraw(...) method. Higher repeat count will get you a more "solid" shadow by trading off the performance.
"Solid" shadow
The rule here is, increment on shadow radius (↑) must always accompany with increment on repeat counter (↑) to achieve the "solid" shadow effect.
Similarly, if you want to gain performance by reducing repeat counter (↓), you have to decrease shadow radius (↓) as well.
Solid shadow TextView
package com.example.solidshadowtext;
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.TextView;
public class SolidShadowTextView extends TextView {
/**
* Shadow radius, higher value increase the blur effect
*/
private static final float SHADOW_RADIUS = 10f;
/**
* Number of times a onDraw(...) call should repeat itself.
* Higher value ends up in more solid shadow (but degrade in performance)
* This value must be >= 1
*/
private static final int REPEAT_COUNTER = 10000;
// Shadow color
private static final int SHADOW_COLOR = 0xff000000;
public SolidShadowTextView(Context context) {
super(context);
init();
}
public SolidShadowTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
#Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < REPEAT_COUNTER; i++) {
super.onDraw(canvas);
}
}
#Override
public void setShadowLayer(float radius, float dx, float dy, int color) {
// Disable public API to set shadow
}
private void init() {
super.setShadowLayer(SHADOW_RADIUS, 0, 0, SHADOW_COLOR);
}
}
Sample
I am trying to display a blue line next to a block of text, pretty much like this:
Here's my code:
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/blue_line" />
blue_line is a jpg file. a blue rectangle. it displays in its original size regardless of the text in the textview. how can i adjust its height dynamically according to the height of the text? like make it shorter when theres little amount of text and longer when there's more text....
You can try doing it in code by setting bounds for the image
textView1.getViewTreeObserver()
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Drawable img = ActivityName.this.getContext().getResources().getDrawable(
R.drawable.blue_line);
img.setBounds(0, 0, img.getIntrinsicWidth() * textView1.getMeasuredHeight() / img.getIntrinsicHeight(), textView1.getMeasuredHeight());
textView1.setCompoundDrawables(img, null, null, null);
textView1.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
The best way to do this is to wrap your drawable in an xml drawable file and set it as a drawable in your text view as follows:
Drawable XML File:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:scaleType="fitXY"
android:src="#drawable/total_calories"/>
TextView in XML:
<TextView
android:id="#+id/title_total_cal"
style="#style/title_stats_textview"
android:drawableLeft="#drawable/total_calories_drawable"/>
Try as below...
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="#drawable/btndr" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/imageView" />
</RelativeLayout>
Simply, Keep your image as 9patch drawable.
You can add android:drawableLeft="#drawable/checkmark" to your textview. You can also set drawablePadding to keep the textview organized.
android:drawableLeft="#drawable/button_icon"
android:drawablePadding="2dip"
Here is the link to create 9patch drawable
<TextView android:text="#string/txtUserName"
android:id="#+id/txtUserName"
android:layout_width="160dip"
android:layout_height="60dip"
android:layout_gravity="center"
android:drawableLeft="#drawable/button_icon"
android:drawablePadding="2dip"
/>
Unfortunately, setBounds was not working for me so I had to do a workaround.
// Turn wanted drawable to bitmap
Drawable dr = getResources().getDrawable(R.drawable.somedrawable);
Bitmap bitmap = ((BitmapDrawable) dr).getBitmap();
// I had a square image with same height and width so I needed only TextView height (getLineHeight)
int size = textView1.getLineHeight();
Drawable d = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap, size, size, true));
textView1.setCompoundDrawables(d, null, null, null);
// Now we can set some spacing between text and image
textView1.setCompoundDrawablePadding(10);
It is not the best solution regarding performance because new bitmap is created, but still works.
You can draw a line with your desired height on drawable canvas and set as left drawable of your TextView. check this out:
public class FullHeightLineDrawable extends Drawable {
private Paint mPaint;
private int mHeight;
public FullHeightLineDrawable(int height) {
mPaint = new Paint();
mPaint.setColor(CompatUtils.getColor(R.color.colorAccent));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(15);
mHeight = height;
}
#Override
public void draw(Canvas canvas) {
canvas.drawLine(0, -mHeight, 0, mHeight, mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return 0;
}
}
Usage:
final Drawable drawable = new FullHeightLineDrawable(getHeight());
mTextView.setTextColor(watchingColor);
mTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
I abstract out this method. It works when the drawable is on the left of the TextView,dynamically scaling. If drawable is on the right side,this method doesn't work, needing to figure out why, but you can just directly use textView.setcompounddrawableswithintrinsicbounds() with the right-size Drawable resource
#RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
public static void ajustCompoundDrawableSizeWithText(final TextView textView, final Drawable leftDrawable, final Drawable topDrawable, final Drawable rightDrawable, final Drawable bottomDrawable) {
textView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if(leftDrawable != null){
leftDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
if(topDrawable != null){
topDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
if(rightDrawable != null){
rightDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
if(bottomDrawable != null){
bottomDrawable.setBounds(0, 0, (int)textView.getTextSize(), (int)textView.getTextSize());
}
textView.setCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
textView.removeOnLayoutChangeListener(this);
}
});
}
I Created a class that extends TextView, and resize the drawables in onPreDrawListener.
public class MyTextView extends AppCompatTextView {
public MyTextView(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
fitCompoundDrawableToLineHeight();
}
private void fitCompoundDrawableToLineHeight() {
OnPreDraw.run(this, () -> {
final Drawable[] cd = getCompoundDrawables();
Arrays.stream(cd)
.filter(drawable -> drawable != null)
.forEach(d -> d.setBounds(0, 0, getLineHeight(), getLineHeight()));
setCompoundDrawables(cd[0], cd[1], cd[2], cd[3]);
});
}
}
// Convenience class that abstracts onPreDraw logic
public class OnPreDraw {
public static void run(View view, Runnable task) {
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (!view.getViewTreeObserver().isAlive()) {
return true;
}
view.getViewTreeObserver().removeOnPreDrawListener(this);
task.run();
return true;
}
});
}
}
You need to make a one horizontal XML layout that has the blue line left and a textview right.
Than use that layout like an item and make a ListView of those items. Something like here, but a bit simpler
This question already has answers here:
How to get text on an ActionBar Icon?
(4 answers)
Closed 4 years ago.
I have a menu item in the action bar. Along with the menu item image, I need to show some number associated with it which will change often. I am not using Action bar sherlock. I don't want to use that. Other than this everything else just works fine. In the shown image, the white icon color icon is mine. I need to generate the number with the red color background dynamically. How can I do that in Android?
Here is the sample image:
Update:
I have this menu item in my menu.xml. This should work like a notification menu item which shows the number of notification count. I set the menu icon like,
menuItem.setIcon(image);
Now, on top of the menu item I need to place one text view which has the total count of notifications.
is it possible to implement this functionality with viewbadger?
Github url
I discovered how to add an actionView to a menu item and retrieve as set values to the view in code.
See here: https://stackoverflow.com/a/16648170/857681
After a lot of trying of nearly all resources on SO I turned to blogs; succesfully. I want to share what worked for me (Api >= 13); source.
Let's start with the sweet code, the way it's used:
public boolean onCreateOptionsMenu(Menu menu) {
//inflate menu
getMenuInflater().inflate(R.menu.menu_my, menu);
// Get the notifications MenuItem and LayerDrawable (layer-list)
MenuItem item = menu.findItem(R.id.action_notifications);
LayerDrawable icon = (LayerDrawable) item.getIcon();
// Update LayerDrawable's BadgeDrawable
Utils2.setBadgeCount(this, icon, 2);
return true;
}
The menu_my.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<item
android:id="#+id/action_notifications"
android:icon="#drawable/ic_menu_notifications"
android:title="Notifications"
app:showAsAction="always" />
</menu>
This class that conveniently makes a BadgeDrawable; its appearance can be modified as well:
public class BadgeDrawable extends Drawable {
private float mTextSize;
private Paint mBadgePaint;
private Paint mTextPaint;
private Rect mTxtRect = new Rect();
private String mCount = "";
private boolean mWillDraw = false;
public BadgeDrawable(Context context) {
//mTextSize = context.getResources().getDimension(R.dimen.badge_text_size);
mTextSize = 12F;
mBadgePaint = new Paint();
mBadgePaint.setColor(Color.RED);
mBadgePaint.setAntiAlias(true);
mBadgePaint.setStyle(Paint.Style.FILL);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER);
}
#Override
public void draw(Canvas canvas) {
if (!mWillDraw) {
return;
}
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
// Position the badge in the top-right quadrant of the icon.
float radius = ((Math.min(width, height) / 2) - 1) / 2;
float centerX = width - radius - 1;
float centerY = radius + 1;
// Draw badge circle.
canvas.drawCircle(centerX, centerY, radius, mBadgePaint);
// Draw badge count text inside the circle.
mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect);
float textHeight = mTxtRect.bottom - mTxtRect.top;
float textY = centerY + (textHeight / 2f);
canvas.drawText(mCount, centerX, textY, mTextPaint);
}
/*
Sets the count (i.e notifications) to display.
*/
public void setCount(int count) {
mCount = Integer.toString(count);
// Only draw a badge if there are notifications.
mWillDraw = count > 0;
invalidateSelf();
}
#Override
public void setAlpha(int alpha) {
// do nothing
}
#Override
public void setColorFilter(ColorFilter cf) {
// do nothing
}
#Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
}
This class that helps to set the number. I recommend implementing even more thods to set badge as date, etc:
public class Utils2 {
public static void setBadgeCount(Context context, LayerDrawable icon, int count) {
BadgeDrawable badge;
// Reuse drawable if possible
Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge);
if (reuse != null && reuse instanceof BadgeDrawable) {
badge = (BadgeDrawable) reuse;
} else {
badge = new BadgeDrawable(context);
}
badge.setCount(count);
icon.mutate();
icon.setDrawableByLayerId(R.id.ic_badge, badge);
}
}
And mui importante a drawable (like a layout) in res/drawable:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/ic_notification"
android:drawable="#drawable/ice_skate"
android:gravity="center" />
<!-- set a place holder Drawable so android:drawable isn't null -->
<item
android:id="#+id/ic_badge"
android:drawable="#drawable/ice_skate" />
</layer-list>
Good lucks!
Here is one thing you can try:
Create a custom Drawable that paint you image in the background and text on top of the image. Check out this post for sample.
Then set this Drawable as the MenuItem background dynamically...
Use action view. It works with both: default ActionBar and ActionBarSherlock.
Here is an example
With this approach you can just create your own View (by inflating some layout for example) and then do whatever you want (change background, change content, add another views dynamically if your action view is subclass of ViewGroup etc.).
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.