I have an image that I would like to slowly fade in; I've played around with the standard animation constructs, but it never seems to work.
(Code for the image:)
#Override
public void draw(Canvas c, Layer layer) {
if ( layer == Layer.BACKGROUND ) {
Animation a = new AlphaAnimation(1.00f, 0.00f);
a.setDuration(4000);
instructions.startAnimation(a);
location.left = (view.getWidth() - instructions.getMinimumWidth() ) /2;
location.right = location.left + instructions.getMinimumWidth();
location.top = view.getHeight() / 2;
location.bottom = location.top + instructions.getMinimumHeight();
instructions.setBounds(location);
instructions.draw(c);
}
//explosions.draw(c, layer);
}
};
... "startAnimation is undefined for the type BitmapDrawable," it says.
The startAnimation is a method of a View, so if you have the bitmap in an ImageView instance imageView, you should call imageView.startAnimation(a);.
Instead of doing it in code, consider doing it using XML: http://developer.android.com/guide/topics/resources/animation-resource.html#View
Related
I have quite easy class that draws circles. I'm giving the parameter, view calculates the rest. All I want to give some delay and fade effect for each during draw to canvas. I reviewed a few articles about animators and handlers but I couldn't figure out. Please, show me some implementations. Thanks.
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
int w = getWidth();
int pl = getPaddingLeft();
int pr = getPaddingRight();
int totalWidth = w - (pl + pr);
major = totalWidth / circleCount;
radius = major / 2;
startPoint = totalWidth / (circleCount * 2);
for (int i = 0; i < circleCount; i++) {
canvas.drawCircle(startPoint + major * i, radius, radius, paint);
}
}
Here's a simple alpha animation of a button view [it makes the button blink](it's not so hard ;O) ):
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
final Animation animation = new AlphaAnimation(1, 0); // Change alpha from fully visible to invisible
animation.setDuration(500); // duration - half a second
animation.setInterpolator(new LinearInterpolator()); // do not alter animation rate
animation.setRepeatCount(Animation.INFINITE); // Repeat animation infinitely
animation.setRepeatMode(Animation.REVERSE); // Reverse animation at the end so the button will fade back in
final Button btn = (Button) findViewById(R.id.but4);//replace this with your view
btn.startAnimation(animation);
You could use the setAlpha(int a) method from the Paint class.
It should work when you do it on a separate Thread with a little time delay in a loop where you count down from 255 to 0.
Here is a code sample where I tried this for earlier versions of Android some years ago :
private final int FADE_TIME = 10; // modify to your needs
private void fade() {
new Thread(new Runnable() {
#Override
public void run() {
try {
int i = 255;
while (i >= 0) {
paint.setAlpha(i);
Thread.sleep(FADE_TIME);
i--;
}
} catch (InterruptedException e) {
// do something in case of interruption
}
}
}).start();
}
Nowadays I would probably use a Handler with postDelayed() for this job, but this should give you an impression of how it can be done.
Google has shown some nice ways that elevation effect are shown on Lollipop here.
android:elevation="2dp"
for buttons,
android:stateListAnimator="#anim/button_state_list_animator"
How can I mimic the elevation effect on pre-Lollipop versions without 3rd party library?
You can mimic the elevation on pre-Lollipop with a official method.
I achieve same effect using,
android:background="#android:drawable/dialog_holo_light_frame"
My tested output:
reference - https://stackoverflow.com/a/25683148/3879847
Thanks to user #Repo..
Update : If you want change color of this drawable try #Irfan answer below ↓
https://stackoverflow.com/a/40815944/3879847
You can't mimic the elevation on pre-Lollipop with a official method.
You can use some drawables to make the shadow in your component. Google uses this way in CardView for example.
The ViewCompat.setElevation(View, int) currently creates the shadow only on API21+. If you check the code behind, this method calls:
API 21+:
#Override
public void setElevation(View view, float elevation) {
ViewCompatLollipop.setElevation(view, elevation);
}
API < 21
#Override
public void setElevation(View view, float elevation) {
}
You can either hack it using a card-view:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/btnGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="#color/accent"
>
<!-- you could also add image view here for icon etc. -->
<TextView
android:id="#+id/txtGetStuff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/textSize_small"
android:textColor="#color/primary_light"
android:freezesText="true"
android:text="Get Stuff"
android:maxWidth="120dp"
android:singleLine="true"
android:ellipsize="end"
android:maxLines="1"
/></android.support.v7.widget.CardView>
Or look at using this third party library: https://github.com/rey5137/Material (see wiki article on button https://github.com/rey5137/Material/wiki/Button)
To bring dynamic, animated shadows to pre-Lollipop devices you have to:
Draw a black shape of a view to a bitmap
Blur that shape using elevation as a radius. You can do that using RenderScript. It's not exactly the method Lollipop's using, but gives good results and is easy to add to existing views.
Draw that blurred shape beneath the view. Probably the best place is the drawChild method.
You also have to override setElevation and setTranslationZ, override child view drawing in layouts, turn off clip-to-padding and implement state animators.
That's a lot of work, but it gives the best looking, dynamic shadows with response animations. I'm not sure why you'd like to achieve that without third party libraries. If you wish, you can analyze sources of Carbon and port the parts you'd like to have in your app:
Shadow generation
private static void blurRenderScript(Bitmap bitmap, float radius) {
Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
blurShader.setRadius(radius);
blurShader.setInput(inAllocation);
blurShader.forEach(outAllocation);
outAllocation.copyTo(bitmap);
}
public static Shadow generateShadow(View view, float elevation) {
if (!software && renderScript == null) {
try {
renderScript = RenderScript.create(view.getContext());
blurShader = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
} catch (RSRuntimeException ignore) {
software = true;
}
}
ShadowView shadowView = (ShadowView) view;
CornerView cornerView = (CornerView) view;
boolean isRect = shadowView.getShadowShape() == ShadowShape.RECT ||
shadowView.getShadowShape() == ShadowShape.ROUND_RECT && cornerView.getCornerRadius() < view.getContext().getResources().getDimension(R.dimen.carbon_1dip) * 2.5;
int e = (int) Math.ceil(elevation);
Bitmap bitmap;
if (isRect) {
bitmap = Bitmap.createBitmap(e * 4 + 1, e * 4 + 1, Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
shadowCanvas.drawRect(e, e, e * 3 + 1, e * 3 + 1, paint);
blur(bitmap, elevation);
return new NinePatchShadow(bitmap, elevation);
} else {
bitmap = Bitmap.createBitmap((int) (view.getWidth() / SHADOW_SCALE + e * 2), (int) (view.getHeight() / SHADOW_SCALE + e * 2), Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
if (shadowView.getShadowShape() == ShadowShape.ROUND_RECT) {
roundRect.set(e, e, (int) (view.getWidth() / SHADOW_SCALE - e), (int) (view.getHeight() / SHADOW_SCALE - e));
shadowCanvas.drawRoundRect(roundRect, e, e, paint);
} else {
int r = (int) (view.getWidth() / 2 / SHADOW_SCALE);
shadowCanvas.drawCircle(r + e, r + e, r, paint);
}
blur(bitmap, elevation);
return new Shadow(bitmap, elevation);
}
}
Drawing a view with a shadow
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (!child.isShown())
return super.drawChild(canvas, child, drawingTime);
if (!isInEditMode() && child instanceof ShadowView && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) {
ShadowView shadowView = (ShadowView) child;
Shadow shadow = shadowView.getShadow();
if (shadow != null) {
paint.setAlpha((int) (ShadowGenerator.ALPHA * ViewHelper.getAlpha(child)));
float childElevation = shadowView.getElevation() + shadowView.getTranslationZ();
float[] childLocation = new float[]{(child.getLeft() + child.getRight()) / 2, (child.getTop() + child.getBottom()) / 2};
Matrix matrix = carbon.internal.ViewHelper.getMatrix(child);
matrix.mapPoints(childLocation);
int[] location = new int[2];
getLocationOnScreen(location);
float x = childLocation[0] + location[0];
float y = childLocation[1] + location[1];
x -= getRootView().getWidth() / 2;
y += getRootView().getHeight() / 2; // looks nice
float length = (float) Math.sqrt(x * x + y * y);
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
x / length * childElevation / 2,
y / length * childElevation / 2);
canvas.translate(
child.getLeft(),
child.getTop());
canvas.concat(matrix);
canvas.scale(ShadowGenerator.SHADOW_SCALE, ShadowGenerator.SHADOW_SCALE);
shadow.draw(canvas, child, paint);
canvas.restoreToCount(saveCount);
}
}
if (child instanceof RippleView) {
RippleView rippleView = (RippleView) child;
RippleDrawable rippleDrawable = rippleView.getRippleDrawable();
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless) {
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
child.getLeft(),
child.getTop());
rippleDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
return super.drawChild(canvas, child, drawingTime);
}
Elevation API backported to pre-Lollipop
private float elevation = 0;
private float translationZ = 0;
private Shadow shadow;
#Override
public float getElevation() {
return elevation;
}
public synchronized void setElevation(float elevation) {
if (elevation == this.elevation)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setElevation(elevation);
this.elevation = elevation;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
#Override
public float getTranslationZ() {
return translationZ;
}
public synchronized void setTranslationZ(float translationZ) {
if (translationZ == this.translationZ)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setTranslationZ(translationZ);
this.translationZ = translationZ;
if (getParent() != null)
((View) getParent()).postInvalidate();
}
#Override
public ShadowShape getShadowShape() {
if (cornerRadius == getWidth() / 2 && getWidth() == getHeight())
return ShadowShape.CIRCLE;
if (cornerRadius > 0)
return ShadowShape.ROUND_RECT;
return ShadowShape.RECT;
}
#Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setTranslationZ(enabled ? 0 : -elevation);
}
#Override
public Shadow getShadow() {
float elevation = getElevation() + getTranslationZ();
if (elevation >= 0.01f && getWidth() > 0 && getHeight() > 0) {
if (shadow == null || shadow.elevation != elevation)
shadow = ShadowGenerator.generateShadow(this, elevation);
return shadow;
}
return null;
}
#Override
public void invalidateShadow() {
shadow = null;
if (getParent() != null && getParent() instanceof View)
((View) getParent()).postInvalidate();
}
Create a 9-patch image with stretchable patches defined on an image with shadow around it.
Add this 9-patch image as a background of your button with a padding so that the shadow is visible.
You can find some pre-defined 9-patch (.9.png) images here or here from where you can select, customize and copy to your project's drawable.
to add #Ranjith Kumar answer
To add background color to the drawable (example button background color), we need to get drawable programatically.
first get the drawable
Drawable drawable = getResources().getDrawable(android.R.drawable.dialog_holo_light_frame);
set the color
drawable.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.color_primary), PorterDuff.Mode.MULTIPLY));
then set it to the view.
view.setBackgroundDrawable(drawable);
in case anyone searching.
u can easily simulate it by declaring a drawable like this -
shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
>
<gradient android:type="linear" android:angle="270" android:startColor="#b6b6b6" android:endColor="#ffffff"/>
</shape>
and use it int ur main xml like -
android:background="#drawable/shadow"
Is it possible to change the current size of circle in ShowCaseView?
What I have in my code is the following:
ShowcaseView.ConfigOptions co = new ShowcaseView.ConfigOptions();
co.hideOnClickOutside = false;
return ShowcaseView.insertShowcaseView(id, activity, title, message, co);
but right now, I'm just showing the default size of ShowCaseView, How can I resize it if so?
thanks!
for more info regarding ShowCaseView, this will be of help..
https://github.com/amlcurran/Showcaseview
I found how to do it, we have to do some changes in the code.
I'm writing this answer while current ShowcaseView release is 5.0
A) scalemultiplier became orphelin, we have to reimplement it.
1) open StandarShowcaseDrawer.java, locate drawShowcase method and modify it as bellow :
#Override
public void drawShowcase(Bitmap buffer, float x, float y, float scaleMultiplier) {
Canvas bufferCanvas = new Canvas(buffer);
//[GIOVA]
//To avoid an infinite exception
if(scaleMultiplier == 0.0f)
scaleMultiplier = 0.001f;
//[/GIOVA]
//[Original]
//bufferCanvas.drawCircle(x, y, showcaseRadius, eraserPaint);
//[/Original]
//[GIOVA]
bufferCanvas.drawCircle(x, y, showcaseRadius * scaleMultiplier, eraserPaint);
//[/GIOVA]
int halfW = getShowcaseWidth() / 2;
int halfH = getShowcaseHeight() / 2;
int left = (int) (x - halfW);
int top = (int) (y - halfH);
showcaseDrawable.setBounds(left, top,
left + getShowcaseWidth(),
top + getShowcaseHeight());
showcaseDrawable.draw(bufferCanvas);
}
NB : note that i've placed some [GIOVA] and [Original] tags, so you can compare changes ;)
2) open NewShowcaseDrawer.java, same job as previous step :
#Override
public void drawShowcase(Bitmap buffer, float x, float y, float scaleMultiplier) {
Canvas bufferCanvas = new Canvas(buffer);
//[GIOVA]
//To avoid an infinite exception
if(scaleMultiplier == 0.0f)
scaleMultiplier = 0.001f;
//[/GIOVA]
eraserPaint.setAlpha(ALPHA_60_PERCENT);
//[Original]
//bufferCanvas.drawCircle(x, y, outerRadius , eraserPaint);
//eraserPaint.setAlpha(0);
//bufferCanvas.drawCircle(x, y, innerRadius , eraserPaint);
//[/Original]
//[GIOVA]
bufferCanvas.drawCircle(x, y, outerRadius * scaleMultiplier, eraserPaint);
eraserPaint.setAlpha(0);
bufferCanvas.drawCircle(x, y, innerRadius * scaleMultiplier, eraserPaint);
//[/GIOVA]
}
B) Now we need to be able to set the value, we'll add a method to the builder.
Open ShowcaseView.java then locate following line :
public static class Builder {
Inside this Builder class, add the following method :
public Builder setScaleMultiplier(float multiplier){
showcaseView.setScaleMultiplier(multiplier);
return this;
}
How to use it :
Pretty simple now, in your activity, when you use Builder to setup your showcaseview, simply call setScaleMultiplier.
Example :
sv = new ShowcaseView.Builder(this, true)
.setTarget(target)
.setContentTitle(getResources().getString(R.string.welcome))
.setContentText(getResources().getString(R.string.welcomDescription))
.setStyle(R.style.MyTheme)
.setScaleMultiplier(0.3f)
.build();
sv.Show();
Use setScaleMultiplier(float scaleMultiplier)
Hope help you!
I'm currently trying to animate the position of a button using a TranslateAnimation. I've read in multiple places that the way to make the actual button move as well as the drawable is to change its layout after the animation. I've done that, but I've run into two problems/solutions:
Either I add setFillAfter( true ). This is nice because the drawable persists after the animation, but when the layout is changed, the drawable is offset from where it should be by the translation distance, while the blank frame of the button is now where it should be.
Add setFillAfter( false ). Doing this and then setting the layout after the animation works like it should, but the icon will flash, I assume this is the delay between the ending of the animation and the refresh of the screen with the new layout parameters. I'm currently using this code, but the flash is unacceptable, so I'd like to find a solution to fix it.
Here's my code currently:
final View aniView = v; // v is some view
TranslateAnimation ani = new TranslateAnimation(0,
240 - v.getLeft() - v.getWidth() / 2,
0,
240 - v.getTop() - v.getHeight() / 2 );
ani.setDuration( 500 );
ani.setFillAfter( false );
ani.setAnimationListener( new AnimationListener() {
public void onAnimationEnd( Animation a ) {
aniView.setTag( new Boolean( true ) );
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( aniView.getWidth(),
aniView.getHeight() );
params.leftMargin = 240 - aniView.getWidth() / 2;
params.topMargin = 240 - aniView.getHeight() / 2;
aniView.setLayoutParams(params);
}
public void onAnimationStart( Animation a ) {}
public void onAnimationRepeat( Animation a ) {}
});
v.animationStart( ani );
I've figured out the solution to my own question, and will post the code for anyone who comes across the same problem. Basically the idea is to set the layout params before hand and essentially reverse the animation. Here's the idea:
TranslateAnimation ani = new TranslateAnimation(v.getLeft() + v.getWidth() / 2 - 240 ,
0,
v.getTop() + v.getHeight() / 2 - 240,
0);
ani.setDuration( 500 );
ani.setFillAfter( true );
ani.setDuration( 1000 );
ani.setFillAfter( true );
ani.setAnimationListener( new AnimationListener() {
public void onAnimationEnd( Animation a ) {
aniView.setTag( new Boolean( true ) );
}
public void onAnimationStart( Animation a ) {}
public void onAnimationRepeat( Animation a ) {}
});
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( v.getWidth(),
v.getHeight() );
params.leftMargin = 240 - aniView.getWidth() / 2;
params.topMargin = 240 - aniView.getHeight() / 2;
v.setLayoutParams(params);
v.startAnimation( set );
Completing the awesome answer from Pete, I had the same problem trying to animate a full activity layout (the flashing in the end of the animation), but I used view.translateX instead of Layout Params... And this solution is also applicable to my case.
I have a custom view where I use transformations. So far so good, function like setRotationY(), setScaleX(), setTranslationY() or even getMatrix() work as expected, I’m able manipulate my view and it displays fine.
Where I hit the wall is that a number of function behave strangely after that. For example function like getHitRect() return totally weird values! This is not helping my touch events.
I tried to overload the function but it is still far from working especially when using rotation or scaling (Translation working fine through). I think this as something to do with the fact that the matrix is expressed in child coordinate, so how can I get it in parents coordinate?
#Override
public void getHitRect(Rect outRect){
RectF rect = new RectF();
rect.top = (float) this.getTop();
rect.bottom = (float) this.getBottom();
rect.left = (float) this.getLeft();
rect.right = (float) this.getRight();
this.getMatrix().mapRect(rect);
rect.round(outRect);
}
Can I get some straighter value directly from some function? Like the new Height, Width, top or bottom.
When overriding the “getChildStaticTransformation” method of the ViewGroup or even using transformation function like setRotationY(), setScaleX(), setTranslationY(), getMatrix() (available from API 11) you are only affecting the rendering Matrix. As a result your custom Child View will return Bounds “Rects” far from where your child is getting draw. This is not an issue most of the time but when you start to be willing to click on it.. trouble are starting... Here is how I workaround the issue. I’m sure there might be a better ways but as I haven’t found many things on the subject here it is.
In the ViewGroup Overload:
public interface Itransformable {
public void setTransformationMatrix(Matrix trsMatrix);
}
#Override
protected boolean getChildStaticTransformation(View child, Transformation t) {
if (child instanceof Itransformable){
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
...
// Do whatever transformation you want here
...
((Itransformable)child).setTransformationMatrix(t.getMatrix());
return true;
} else {
return false;
}
}
Here is the Child Custom View:
Note that I'm not storing directly the Transformation Matrix in the custom view but instead the transformed Rect. If you want to go for storing the matrix (i.e. for later transformation like point...) you might need to clone it as the matrix will get altered in some strange way like it is recycle or something.
public class MyCustomView extends View implements MyViewGroup.Itransformable{
private Rect mViewRect = null;
public void setTransformationMatrix(Matrix trsMatrix){
if (trsMatrix!=null){
RectF rect = new RectF();
rect.top = 0;
rect.bottom = (float) this.getHeight();
rect.left = 0;
rect.right = (float) this.getWidth();
trsMatrix.mapRect(rect);
rect.offset((float) this.getLeft(), (float) this.getTop());
if (mViewRect == null) mViewRect = new Rect();
rect.round(mViewRect);
}
}
public Rect getTransformatedRect() {
if (mViewRect!=null){
// OutOfScreen WorkArround - As the view is not Displayed, mViewRect doesn't get updated.
if(getRight() < 0 || getLeft() > mParentWidth){
return new Rect(getLeft(),getTop(),getRight(),getBottom());
} else {
return mViewRect;
}
} else {
return new Rect(getLeft(),getTop(),getRight(),getBottom());
}
}
#Override
public void getHitRect(Rect outRect){
if (mViewRect == null){
super.getHitRect(outRect);
} else {
outRect.set(getTransformatedRect());
}
}