Adding a Foreground Selector to a View/ViewGroup on Android
Join the DZone community and get the full member experience.
Join For Freeeveryone has already seen the android cards touch feedback. the selector is drawn in the foreground instead of in the background (as we usually implement it). this effect is in fact pretty simple to implement, and is already implemented in some cases.
here is a screenshot of the press state effect in the google play app:
i. if your view is a
framelayout
this is the easiest way to add a foreground selector because there is a
method
for that! indeed, you just have to pass your selector as
android:foreground
in your xml or programmatically, calling
setforeground(drawable)
.
ii. if your view is not a
framelayout
don’t worry, this is pretty simple. basically, we just have to set right state to the selector (pressed, focused, etc.), set the bounds and draw it after the view itself. in that way, the selector will be drawn after, and as a consequence, over the view.
changing the state
in the
view
class, a method is called each time the state of the view changes. this method is
drawablestatechanged()
(
doc here
).
@override protected void drawablestatechanged() { super.drawablestatechanged(); mforegroundselector.setstate(getdrawablestate()); //redraw invalidate(); }
updating the drawable bounds
a method is called each time the size of the view changes. this method is
onsizechanged(int, int, int, int)
(
doc here
).
@override protected void onsizechanged(int width, int height, int oldwidth, int oldheight) { super.onsizechanged(width, height, oldwidth, oldheight); mforegroundselector.setbounds(0, 0, width, height); }
drawing the selector
there are 2 cases:
1. your view is
not
a
viewgroup
the selector has to be drawn after calling
ondraw(canvas canvas)
@override protected void ondraw(canvas canvas) { super.ondraw(canvas); mforegroundselector.draw(canvas); }
2. your view is a
viewgroup
the selector has to be drawn after all his children, that means after calling
dispatchdraw(canvas canvas)
@override protected void dispatchdraw(canvas canvas) { super.dispatchdraw(canvas); mforegroundselector.draw(canvas); }
drawing an animated drawable
if your drawable is animated, there is a bit more to do. let’s suppose that we have a selector with the attribute
android:exitfadeduration
. that means when the selector changes its state, the old state will fade out.
we first have to move the draw method of the selector from
ondraw()
(for views) or
dispatchdraw()
(for viewgroups) to the
draw(canvas)
method, just like this:
@override public void draw(canvas canvas) { super.draw(canvas); mforegrounddrawable.draw(canvas); }
then we have to override
jumpdrawablestocurrentstate
to indicate our selector to do transition animations between states, and
verifydrawable
to indicate the view we are displaying our own drawable.
@override protected boolean verifydrawable(drawable who) { return super.verifydrawable(who) || (who == mforegrounddrawable); } @targetapi(11) @override public void jumpdrawablestocurrentstate() { super.jumpdrawablestocurrentstate(); mforegrounddrawable.jumptocurrentstate(); }
but what is doing jumptocurrentstate()? let’s see a bit of source code, in the
drawablecontainer
class.
@override public void jumptocurrentstate() { boolean changed = false; if (mlastdrawable != null) { mlastdrawable.jumptocurrentstate(); mlastdrawable = null; changed = true; } if (mcurrdrawable != null) { mcurrdrawable.jumptocurrentstate(); mcurrdrawable.mutate().setalpha(malpha); } if (mexitanimationend != 0) { mexitanimationend = 0; changed = true; } if (menteranimationend != 0) { menteranimationend = 0; changed = true; } if (changed) { invalidateself(); } }
we can notice that
jumptocurrentstate()
calls
invalidateself()
. and here is the
invalidateself()
method source code:
/** * use the current {@link callback} implementation to have this drawable * redrawn. does nothing if there is no callback attached to the * drawable. * * @see callback#invalidatedrawable * @see #getcallback() * @see #setcallback(android.graphics.drawable.drawable.callback) */ public void invalidateself() { final callback callback = getcallback(); if (callback != null) { callback.invalidatedrawable(this); } }
we clearly see that if the callback is not set, the drawable won’t be redrawn. so let’s set a callback when we init our selector.
private void init(context context) { mforegrounddrawable = getresources().getdrawable(r.drawable.myselector); //set a callback, or the selector won't be animated mforegrounddrawable.setcallback(this); }
your selector should fade out now!
extra
retrieve the default background, and set it as the foreground
you can get the default background selector of your theme and set it as your foreground selector if you want:
typedarray a = getcontext().obtainstyledattributes(new int[]{android.r.attr.selectableitembackground}); mforegrounddrawable = a.getdrawable(0); if (mforegrounddrawable != null) { mforegrounddrawable.setcallback(this); } a.recycle();
Published at DZone with permission of Antoine Merle, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
SRE vs. DevOps
-
How To Integrate Microsoft Team With Cypress Cloud
-
Why You Should Consider Using React Router V6: An Overview of Changes
-
Top 10 Engineering KPIs Technical Leaders Should Know
Comments