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.
Related
I have used android.support.design.widget.TextInputLayout to make a password input that allows the user to toggle readability on the password. The xml is as follows:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hintEnabled="false"
app:passwordToggleDrawable="#drawable/password_toggle_selector"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="#+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
The drawable selector is as described by How to customize android passwordToggleDrawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/password_toggle_show"
android:state_checked="true"/>
<item android:drawable="#drawable/password_toggle_hide"/>
</selector>
The issue is that the custom drawable becomes really large. Not larger than the edittext, but rather it seems to maximize its size while still fitting inside it (so, it seems to be bounded by the height of the element). However, if I leave the passwordToggleDrawable property unset, the drawable for the toggle is sized as is normal for android (I am sure you have seen the icon in other apps before). After much searching I have found a way to resize the custom one, but I am not happy with how its done (requires 2 extra xml files per drawable) and it only works for API 23+.
I would like to know if there is a good way to set the size of the drawable, or better yet, make it target the size of the default drawable?
I have tried setting the padding of the EditText as the source of TextInputLayout says that it gets the four paddings from it and apply to the mPasswordToggleView (line 1143), but it made no change on the icon and (as expected) also affected the padding of the EditText. I have tried setting minheight to 0. I have also tried changing between EditText and TextInputEditText (using the latter now as it seems to be recommended). I have tried switching the layout_height properties to wrap_content. I have tried scaling the drawable using xml's <scale> tag with the scale properties set. I have tried similarly with the <inset> tag. But none of those methods works.
The way I found (and am currently using) to resize the drawable that actually works is by using the xml tag <layer-list>, while setting the width and height properties. Then the <selector> xml file references those resized drawables instead of the png ones. But I don't like this solution because as I mentioned it requires API 23 and because of that results in a total of 4 extra xml files. It also sets the width and height by themselves, instead of keeping the ratio locked.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/password_toggle_hide"
android:width="22dp"
android:height="15dp"/>
</layer-list>
TL;DR
How do I set the size of a custom passwordToggleDrawable in TextInputLayout? Preferably to same size as the default drawable.
I know this is an old question, but I faced the same problem and I believe I figure out a simple solution for this.
I'm using the TextInputLayout for the newest material library, and the only thing that I did was to find the reference for the endIcon from the TextInputLayout and change it's minimum dimensions.
val dimension = //here you get the dimension you want to
val endIconImageView = yourTextInputLayout.findViewById<ImageView>(R.id.text_input_end_icon)
endIconImageView.minimumHeight = dimension
endIconImageView.minimumWidth = dimension
yourTextInputLayout.requestLayout()
Important things to notice:
I did this on the OnFinishedInflated from a custom TextInputLayout, but I believe it will work fine on some activity class.
Cheers!
I face same problem. To avoid this situation I used png and set them based dpi like drawable-hdpi, drawable-mdpi etc. Also make those drawable as per radio. Hope that this tricks also work for you.
I were unable to find any solution to the question I actually asked, but I decided to instead solve the issue by disregarding the "in InputTextLayout" part of the question and implemented my own version of the class.
Mostly it is just a copy of InputTextLayout (sadly that class doesnt translate well for subclassing as everything is private) but with most of the stuff I dont need removed, and more importantly, with the CheckableImageButton mPasswordToggleView changed to a ViewGroup containing a View.
The ViewGroup is the clickable button, and handles setMinimumDimensions to keep the clickable area at min 48 dp, like the original did through design_text_input_password_icon.xml. This also makes small drawables not hug the right side of the screen as they are centered in the clickable area, giving the margin that the default drawable appears to have.
The View (or more precisely, a new subclass of it I called CheckableView) is the actual drawable (setBackground()), replacing the CheckableImageButton as the container of the drawable that lets it switch based on state_checked selector.
The xml-property passwordToggleSize allows a dimension to be set, which is used to scale the drawable. I opted to only have one value instead of width&height, and the drawable scales with its ratio locked such that its greatest dimension matches the dimension specified. I made the default size 24dp, as is specified for the default-drawable in design_ic_visibility.xml.
PasswordToggleLayout.java:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.mylifediary.android.client.R;
public class PasswordToggleLayout extends LinearLayout {
// Default values from InputTextLayout's drawable and inflated layout
final int BUTTON_MIN_SIZE = 48; // The button is 48 dp at minimum.
final int DEFAULT_DRAWABLE_SIZE = 24; // The default drawable is 24 dp.
int mButtonMinSize;
final FrameLayout mInputFrame;
EditText mEditText;
private boolean mPasswordToggleEnabled;
private Drawable mPasswordToggleDrawable;
private CharSequence mPasswordToggleContentDesc;
ViewGroup mPasswordToggleViewGroup;
CheckableView mPasswordToggleView;
private boolean mPasswordToggledVisible;
private int mPasswordToggleSize;
private Drawable mPasswordToggleDummyDrawable;
private Drawable mOriginalEditTextEndDrawable;
private ColorStateList mPasswordToggleTintList;
private boolean mHasPasswordToggleTintList;
public PasswordToggleLayout(Context context) {
this(context, null);
}
public PasswordToggleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasswordToggleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
mButtonMinSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, BUTTON_MIN_SIZE,
getResources().getDisplayMetrics());
mInputFrame = new FrameLayout(context);
mInputFrame.setAddStatesFromChildren(true);
addView(mInputFrame);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PasswordToggleLayout, defStyleAttr,
R.style.Widget_Design_TextInputLayout);
mPasswordToggleEnabled = a.getBoolean(
R.styleable.PasswordToggleLayout_passwordToggleEnabled, false);
mPasswordToggleDrawable = a.getDrawable(
R.styleable.PasswordToggleLayout_passwordToggleDrawable);
mPasswordToggleContentDesc = a.getText(
R.styleable.PasswordToggleLayout_passwordToggleContentDescription);
if (a.hasValue(R.styleable.PasswordToggleLayout_passwordToggleTint)) {
mHasPasswordToggleTintList = true;
mPasswordToggleTintList = a.getColorStateList(
R.styleable.PasswordToggleLayout_passwordToggleTint);
}
mPasswordToggleSize = a.getDimensionPixelSize(
R.styleable.PasswordToggleLayout_passwordToggleSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_DRAWABLE_SIZE, getResources().getDisplayMetrics()));
a.recycle();
applyPasswordToggleTint();
}
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
throw new IllegalArgumentException(
"We already have an EditText, can only have one");
}
mEditText = editText;
final boolean hasPasswordTransformation = hasPasswordTransformation();
updatePasswordToggleView();
}
private void updatePasswordToggleView() {
if (mEditText == null) {
// If there is no EditText, there is nothing to update
return;
}
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
// Keep ratio
double w = mPasswordToggleDrawable.getIntrinsicWidth();
double h = mPasswordToggleDrawable.getIntrinsicHeight();
double scale = mPasswordToggleSize / Math.max(w,h);
int scaled_width = (int) (w * scale);
int scaled_height = (int) (h * scale);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END | Gravity.RIGHT);
FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(
scaled_width, scaled_height, Gravity.CENTER);
mPasswordToggleViewGroup = new FrameLayout(this.getContext());
mPasswordToggleViewGroup.setMinimumWidth(mButtonMinSize);
mPasswordToggleViewGroup.setMinimumHeight(mButtonMinSize);
mPasswordToggleViewGroup.setLayoutParams(lp);
mInputFrame.addView(mPasswordToggleViewGroup);
mPasswordToggleViewGroup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
mPasswordToggleView = new CheckableView(this.getContext());
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mPasswordToggleView.setLayoutParams(lp2);
mPasswordToggleViewGroup.addView(mPasswordToggleView);
}
if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
// We should make sure that the EditText has the same min-height
// as the password toggle view. This ensure focus works properly,
// and there is no visual jump if the password toggle is enabled/disabled.
mEditText.setMinimumHeight(
ViewCompat.getMinimumHeight(mPasswordToggleViewGroup));
}
mPasswordToggleViewGroup.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
// Need to add a dummy drawable as the end compound drawable so that
// the text is indented and doesn't display below the toggle view.
if (mPasswordToggleDummyDrawable == null) {
mPasswordToggleDummyDrawable = new ColorDrawable();
}
// Important to use mPasswordToggleViewGroup, as mPasswordToggleView
// wouldn't replicate the margin of the default-drawable.
mPasswordToggleDummyDrawable.setBounds(
0, 0, mPasswordToggleViewGroup.getMeasuredWidth(), 1);
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
// Store the user defined end compound drawable so that we can restore it later
if (compounds[2] != mPasswordToggleDummyDrawable) {
mOriginalEditTextEndDrawable = compounds[2];
}
TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0],
compounds[1], mPasswordToggleDummyDrawable, compounds[3]);
// Copy over the EditText's padding so that we match
mPasswordToggleViewGroup.setPadding(mEditText.getPaddingLeft(),
mEditText.getPaddingTop(), mEditText.getPaddingRight(),
mEditText.getPaddingBottom());
} else {
if (mPasswordToggleViewGroup != null
&& mPasswordToggleViewGroup.getVisibility() == VISIBLE) {
mPasswordToggleViewGroup.setVisibility(View.GONE);
}
if (mPasswordToggleDummyDrawable != null) {
// Make sure that we remove the dummy end compound drawable if
// it exists, and then clear it
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
if (compounds[2] == mPasswordToggleDummyDrawable) {
TextViewCompat.setCompoundDrawablesRelative(mEditText,
compounds[0], compounds[1],
mOriginalEditTextEndDrawable, compounds[3]);
mPasswordToggleDummyDrawable = null;
}
}
}
}
private void applyPasswordToggleTint() {
if (mPasswordToggleDrawable != null && mHasPasswordToggleTintList) {
mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
if (mPasswordToggleView != null
&& mPasswordToggleView.getBackground() != mPasswordToggleDrawable) {
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
}
}
}
private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
if (mPasswordToggleEnabled) {
// Store the current cursor position
final int selection = mEditText.getSelectionEnd();
if (hasPasswordTransformation()) {
mEditText.setTransformationMethod(null);
mPasswordToggledVisible = true;
} else {
mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
mPasswordToggledVisible = false;
}
mPasswordToggleView.setChecked(mPasswordToggledVisible);
if (shouldSkipAnimations) {
mPasswordToggleView.jumpDrawablesToCurrentState();
}
// And restore the cursor position
mEditText.setSelection(selection);
}
}
private boolean hasPasswordTransformation() {
return mEditText != null
&& mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
}
private boolean shouldShowPasswordIcon() {
return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
}
#Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
// Make sure that the EditText is vertically at the bottom,
// so that it sits on the EditText's underline
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
flp.gravity = Gravity.CENTER_VERTICAL
| (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
mInputFrame.addView(child, flp);
// Now use the EditText's LayoutParams as our own and update them
// to make enough space for the label
mInputFrame.setLayoutParams(params);
setEditText((EditText) child);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updatePasswordToggleView();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isPasswordToggledVisible = mPasswordToggledVisible;
return ss;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isPasswordToggledVisible) {
passwordVisibilityToggleRequested(true);
}
requestLayout();
}
static class SavedState extends AbsSavedState {
boolean isPasswordToggledVisible;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
isPasswordToggledVisible = (source.readInt() == 1);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isPasswordToggledVisible ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public static class CheckableView extends View {
private final int[] DRAWABLE_STATE_CHECKED =
new int[]{android.R.attr.state_checked};
private boolean mChecked;
public CheckableView(Context context) {
super(context);
}
public CheckableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CheckableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
#Override
public int[] onCreateDrawableState(int extraSpace) {
if (mChecked) {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace
+ DRAWABLE_STATE_CHECKED.length), DRAWABLE_STATE_CHECKED);
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
}
And then in an attrs.xml:
<declare-styleable name="PasswordToggleLayout">
<attr name="passwordToggleEnabled" format="boolean"/>
<attr name="passwordToggleDrawable" format="reference"/>
<attr name="passwordToggleContentDescription" format="string"/>
<attr name="passwordToggleTint" format="color"/>
<attr name="passwordToggleSize" format="dimension"/>
</declare-styleable>
Same issue for me. The problem comes from the gradle material API implementation:
implementation 'com.google.android.material:material:1.1.0'
downgrade to version 1.0.0 fixes the issue:
implementation 'com.google.android.material:material:1.0.0'
I have looked at the following:
imagebutton with #null background (transparent)
How to prevent onClick method on transparent portion of a PNG-loaded ImageView
And countless others, so I must be missing it.
I have an ImageButton. The image represented by the button is a PNG with transparency. Everything displays great. However, when I click the button on the transparency, the click event fires. That is not what I want at all.
I am looking for a solution, and it's probably obvious and I am missing it, where the transparency does not count in the hit test of the button.
I want to do this all in code, not in xml.
So far I am initializing my ImageButton like this. This is all in Xamarin.Android, but that shouldn't matter. The syntax will be C# instead of Java.
// make the states:
var states = new StateListDrawable();
states.AddState(new int[] { -Android.Resource.Attribute.StateEnabled }, new BitmapDrawable(Context.Resources, disabledImage));
states.AddState(StateSet.WildCard.ToArray(), drawable);
// setup the button
var button = new Android.Widget.ImageButton(Context);
button.SetBackgroundColor(Android.Graphics.Color.Transparent);
button.SetPadding(0, 0, 0, 0);
button.SetImageDrawable(states);
// I am using this in a custom renderer, so these are my
// click handlers. I don't think that matters, but maybe it does?
button.SetOnClickListener(ButtonClickListener.Instance.Value);
button.SetOnTouchListener(ButtonTouchListener.Instance.Value);
button.Tag = this;
Again, everything displays perfectly, the only thing is the click is triggered on the transparent part of the image, which is what I don't want.
EDIT
My Click/Touch Listener Code. This is pulled from the Platform Button Render in Xamarin.Forms
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs#L251-L284
class ButtonClickListener : Object, IOnClickListener
{
public static readonly Lazy<ButtonClickListener> Instance = new Lazy<ButtonClickListener>(() => new ButtonClickListener());
public void OnClick(AView v)
{
var renderer = v.Tag as MyButtonRenderer;
if (renderer != null)
((IButtonController)renderer.Element).SendClicked();
}
}
class ButtonTouchListener : Object, IOnTouchListener
{
public static readonly Lazy<ButtonTouchListener> Instance = new Lazy<ButtonTouchListener>(() => new ButtonTouchListener());
public bool OnTouch(AView v, AMotionEvent e)
{
var renderer = v.Tag as MyButtonRenderer;
if (renderer != null)
{
var buttonController = renderer.Element as IButtonController;
if (e.Action == AMotionEventActions.Down)
{
buttonController?.SendPressed();
}
else if (e.Action == AMotionEventActions.Up)
{
buttonController?.SendReleased();
}
}
return false;
}
}
Things I have tried: in the OnTouchListener
Attempt to get renderer.Control.DrawingCache so I could get a Bitmap and test for a transparent pixel. The DrawingCache always returns null. When setting up the button, I also button.DrawingCacheEnabled = true;
If the DrawingCache was null in the OnTouchListener attempt to build it and grab it
For Example:
if (cache == null)
{
renderer.Control.BuildDrawingCache();
cache = renderer.Control.DrawingCache;
}
Still always null.
Attempt to draw the control to a bitmap to test for the transparent pixel. This was a bad idea generally speaking, but I just wanted to see if it worked.
For Example:
Bitmap bitmap = Bitmap.CreateBitmap(renderer.Control.Width, renderer.Control.Height, Bitmap.Config.Argb8888);
Canvas canvas = new Canvas(bitmap);
renderer.Control.Draw(canvas);
In the above cases, once I had a, or thought I had, a Bitmap representation I would check the pixel like this:
int color = bitmap.GetPixel((int)e.GetX(), (int)e.GetY());
if (color == Android.Graphics.Color.Transparent)
return false;
The above attempts were in service of trying this methodology:
https://stackoverflow.com/a/19566795/1060314
Nothing above seemed to work. I may have been on the correct path but just missed something critical along the way.
I ended up subclassing ImageButton, and this did the trick.
using System;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Util;
using Android.Widget;
using AMotionEvent = Android.Views.MotionEvent;
namespace MyProject.Droid.Renderers
{
public class MDImageButton : Android.Widget.ImageButton
{
public MDImageButton(Context context) : this(context, null)
{ }
public MDImageButton(Context context, IAttributeSet attrs) : this(context, attrs, Android.Resource.Attribute.ImageButtonStyle)
{ }
public MDImageButton(Context context, IAttributeSet attrs, int defStyleAttr) : this(context, attrs, defStyleAttr, 0)
{ }
public MDImageButton(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
{
Focusable = true;
}
public override bool OnTouchEvent(AMotionEvent e)
{
var view = (ImageView)this;
var currentState = ((StateListDrawable)this.Drawable).Current;
if (currentState is BitmapDrawable)
{
var x = (int)e.GetX();
var y = (int)e.GetY();
if (isPixelTransparent(x, y))
return false;
else
return base.OnTouchEvent(e);
}
return base.OnTouchEvent(e);
}
private bool isPixelTransparent(int x, int y)
{
var currentState = ((StateListDrawable)this.Drawable).Current;
Bitmap bmp = ((BitmapDrawable)currentState).Bitmap;
int color = Android.Graphics.Color.Transparent;
try
{
color = bmp.GetPixel(x, y);
}
catch (Exception e)
{
// x or y exceed the bitmap's bounds.
// Reverts the View's internal state from a previously set "pressed" state.
Pressed = false;
}
// Ignores touches on transparent background.
if (color == Android.Graphics.Color.Transparent)
return true;
else
return false;
}
}
}
I found I needed to do it at this level not the Renderer because while:
button.SetOnClickListener(ButtonClickListener.Instance.Value);
button.SetOnTouchListener(ButtonTouchListener.Instance.Value);
does the right thing, the OnClickListener is invoked regardless of what the TouchListener does.
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.
I need to create a custom view which is able to swipe between different views. I can't use ViewPager or the like, since it hasn't the transitions and features I need.
I started with this:
public final class DynamicSwipeView extends View {
private View view;
public DynamicSwipeView(Context context) {
super(context);
}
public void setPage(View view) {
this.view = view;
}
#Override
protected void onDraw(final Canvas canvas) {
if (view != null) {
view.draw(canvas);
}
}
}
But the other view (which works if used directly) isn't drawn. When I assign this swipe-view a background-color in the constructor, it's displayed in that color instead of white, but the other view isn't drawn nevertheless.
Here's a version I'm using written in Kotlin and it works nicely, tested on API 19, 25, and 28.
I'm using it for a floating header row for a TableLayout that I want anchored to the top of the screen. I assign viewToDraw in an ViewTreeObserver.onGlobalLayoutListener so I know the header row is laid out. This was convenient for me as I was listening to the view tree anyway, but this could be brought into the CopyView to invalidate() when necessary. The items in the header row and the CopyView are set to a fixed height, but you could expand on this to copy view dimensions too.
class CopyView #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
var viewToDraw: View? = null
set(value) {
field = value
background = value?.background
invalidate()
}
override fun onDraw(canvas: Canvas?) {
viewToDraw?.draw(canvas)
}
}
try this:
public final class DynamicSwipeView extends View {
private View view;
public DynamicSwipeView(Context context) {
super(context);
}
public void setPage(View view) {
this.view = view;
this.invalidate();
}
#Override
protected void onDraw(final Canvas canvas) {
super.draw(canvas);
if (view != null) {
view.draw(canvas);
}
}
}
this is just a suggestion, I haven't tried it.
If it doesn't work, you should use a list of view and manage visibility of these views (current view = VISIBLE, others GONE). It could be easier to do like that.
I want to develop my application with a simple animation. I have used a lot of source from: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/Arcs.html
My code:
public class Animation extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myFramePaint;
private RectF bigOval;
private float myStart;
private float mySweep;
private static final float SWEEP_INC = 1;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
myPaint.setColor(Color.RED);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.BLACK);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter, Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, useCenter, paint);
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
mySweep -= SWEEP_INC;
invalidate();
}
}}
I'm using my view in this way in my xml file:
<view
class="go.android.Animation$AnimView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
It works properly. I know that it isn't custom animation. But is this possible to set speed of this animation (in ms or seconds)? Or to stop this animation (for example in the OnClick or OnTouch Listener), and get to know when this animation has finished?
I also want to get first whole circle, and on the end of animation - lack of circle. Simply to change direction of this animation. Is this possible?
I don't want to use frame-by-frame animation. I want to get continuous animation. Is there any possibilities to get similar animation (with setting speed, etc...)
I also want to animate not only a color but rather a round drawable.
Thank you in advance. Sorry for my English skill.
There might be better and more precise ways to control your animation than this (you could look into using OpenGL), but you can change the speed and direction of the animation relatively easily given your existing code.
The speed is controlled by the SWEEP_INC field (which stands for Sweep Increment I would guess). Every time the onDraw() method is called, it increases the size of the wedge by 1 degree. If you want the animation to go 5 times as fast, set SWEEP_INC to 5 instead of 1. If you want the animation to go half as fast, set SWEEP_INC to 0.5.
You can also just set a flag to reverse the animation each time it finishes. I've modified your code to reverse each time it reaches the end, using the addToCircle boolean.
public class Animation extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myFramePaint;
private RectF bigOval;
private float myStart;
private float mySweep;
private float SWEEP_INC = 1;
//Use this flag to control the direction of the arc's movement
private boolean addToCircle = true;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
myPaint.setColor(Color.RED);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.BLACK);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter, Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, useCenter, paint);
}
public void setIncrement(float newIncrement)
{
SWEEP_INC = newIncrement;
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
//If the arc is currently getting bigger, decrease the value of mySweep
if(addToCircle)
{
mySweep -= SWEEP_INC;
}
//If the arc is currently getting smaller, increase the value of mySweep
else
{
mySweep += SWEEP_INC;
}
//If the animation has reached the end, reverse it
if(mySweep%360 == 0)
{
addToCircle = !addToCircle;
}
invalidate();
}
}
}
EDIT
If you remove the final and static modifiers from SWEEP_INC you can change the speed of the animation at runtime using the setIncrement(float newIncrement) function I've added.
You can stop the animation by calling setIncrement(0).
However, I don't think there is a constant rate at which onDraw is called. So I don't know of any way to assign a duration in seconds to this animation. For that you may want to look into more sophisticated methods of animation.