I'm trying to add custom transition to fragments. As the Link suggested , the proper solution is to create a custom view as fragment container and then by animating the new added property , make fragment's transition run. but absolutely its on java. I implemented that as below in C# and Xamarin:
class SmartFrameLayout : FrameLayout
{
public SmartFrameLayout(Context context) : base(context) { }
public SmartFrameLayout(Context context, IAttributeSet attrs) : base(context, attrs) { }
public SmartFrameLayout(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr) { }
public SmartFrameLayout(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) { }
//public float getXFraction()
//{
// if (Width == 0) return 0;
// return GetX() / Width;
//}
//public void setXFraction(float fraction)
//{
// Log.Debug("Fraction", fraction.ToString());
// float xx = GetX();
// SetX(xx * fraction);
//}
//private float XFraction;
public float XFraction
{
get {
if (Width == 0) return 0;
return GetX() / Width;
}
set {
float xx = GetX();
SetX(xx * value);
}
}
}
As you can see, first I tried to implement that same as the tutorial (Except that c# doesn't support read-only local variable as a "final" replacement!)
but in objectAnimator the property did not called properly. Then I think maybe using C# property will solve the problem. But it didn't.
Here is my animation xml file, named "from_right.xml":
<?xml version="1.0" encoding="utf-8" ?>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:propertyName="xFraction"
android:valueType="floatType"
android:valueFrom="1.0"
android:valueTo="0"
android:duration="500"/>
I changed propertyName to "XFraction" or even anything else, but the results was the same.
Using "x" as propertyName and "1000" as valueFrom works well.
So I figured out the main problem is that the objectAnimator could not call setXFraction at all!
Please tell me what I'm doing wrong, or if there is a better solution to get exactly the screen width for valueFrom in objectAnimator !
You need to expose the setXFraction and getXFraction methods to Java; they are currently only within managed code and are not accesible to the Java VM.
Use the [Export] attribute to expose these methods to Java so that the animator can use them:
[Export]
public float getXFraction()
{
if (Width == 0) return 0;
return GetX() / Width;
}
[Export]
public void setXFraction(float fraction)
{
Log.Debug("Fraction", fraction.ToString());
float xx = GetX();
SetX(xx * fraction);
}
This will result in the following Java code being generated in SmartFrameLayouts Android Callable Wrapper:
public float getXFraction ()
{
return n_getXFraction ();
}
private native float n_getXFraction ();
public void setXFraction (float p0)
{
n_setXFraction (p0);
}
private native void n_setXFraction (float p0);
Related
I am creating an android application where each fragment is bind to a custom left to right slide animation. That i achieved using Custom Property Animation(FractionLinearLayout.java class given below). My app has several Fragment, switching among these fragment is followed by a right to left slide animation. Everything is working perfect untill i disable animation from Developer Options for testing purpose.
Nothing appears when i disable animations. I can see logcats means app is working perfect just views are not being loaded, (May be) because of the custom properties FractionTranslationX and FractionTranslationY.
Has anyone gone through this problem? Thanks in advance.
FractionLinearLayout.java: custom class for animation
public class FractionLinearLayout extends LinearLayout {
DisplayMetrics matrics = getContext().getResources().getDisplayMetrics();
public FractionLinearLayout(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public FractionLinearLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public FractionLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public float getFractionTranslationX() {
return getWidth() > 0 ? super.getTranslationX() / getWidth() : Float.MAX_VALUE;
}
public void setFractionTranslationX(float translationX) {
int width = getWidth();
super.setTranslationX(width > 0 ? width * translationX : Float.MAX_VALUE);
}
public float getFractionTranslationY() {
return getHeight() > 0 ? super.getTranslationX() / getHeight() : Float.MAX_VALUE;
}
public void setFractionTranslationY(float translationY) {
int height = getHeight();
super.setTranslationY(height > 0 ? height * translationY : Float.MAX_VALUE);
}
public float getAnimWidth() {
return getLayoutParams().width;
}
public void setAnimWidth(int animWidth) {
getLayoutParams().width = animWidth;
requestLayout();
}
}
layout file of fragment:
fragment_main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.bbi.views.FractionLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/esc_app_background"
android:orientation="vertical">
...........
...........
</com.bbi.views.FractionLinearLayout>
Custom animation xml file:
alide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/decelerate_interpolator">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="#integer/animation_time"
android:propertyName="fractionTranslationX"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" />
</set>
slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/accelerate_interpolator">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="#integer/animation_time"
android:propertyName="fractionTranslationX"
android:valueFrom="0"
android:valueTo="-1"
android:valueType="floatType" />
</set>
Code to add fragment on Activity:
getFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_left;, R.anim.slide_out_left)
.replace(R.id.frameTopContainer, newsToolFragment)
.commitAllowingStateLoss();
When i replace com.views.FractionLinearLayout to LinearLayout in fragment_main_layout.xml, everything works fine(not animation obvious as i have disabled animation and removed property custom animation).
Finally solved my issue by reading animation_scale flag and removing setCustomAnimations() method according to that.
The problem is when you disabled the animation, the animator will call setFractionTranslationX() to pass the initial value and final value at the same time, and it would be a very early moment that your view not yet have width and height, so you will translated your view to Float.MAX_VALUE every time.
To correctly set your view x and y position whenever animation enable or not, you can :
private float mXFraction;
private float mYFraction;
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
setFractionTranslationX(mXFraction);
setFractionTranslationY(mYFraction);
}
public float getFractionTranslationX() {
return mXFraction;
}
public void setFractionTranslationX(float xFraction) {
mXFraction = xFraction;
int width = getWidth();
if (width > 0) setTranslationX(xFraction * width);
}
public float getFractionTranslationY() {
return mYFraction;
}
public void setFractionTranslationY(float yFraction) {
mYFraction = yFraction;
int height = getHeight();
if (height > 0) setTranslationY(yFraction * height);
}
I'm writing a calendar application for Android. The calendar needs to a have a day display similar to the default application, or MS outlook: a grid showing a line for each hour, and the appointments shown as rectangles.
Here's a similar sample image from Google Images:
I downloaded the source code for the calendar app from Google's Android Open Source Project, and saw that they implemented this display as a custom view which simplay uses Canvas.drawRect() to draw the rectangles, and then they implemented their own hit-test when the user clicks, to see which appointment was clicked.
I already wrote some of that stuff on my own and it works great, and isn't too complicated.
The problem is that I need the different lines of text inside the rectangles (the subject, the time) to be links to various functionality, and I'm wondering how I can do that.
When I draw, I already create Rects for each appointment. I was thinking I could create Rects for each piece of text as well, cache all of these in an ArrayList, and then perform the histtest against the cached rects. I'm only afraid this whole thing will be too heavy... does this sound like a solid design?
Or should I avoid the custom drawing altogether and programmatically generate and place views (TextViews maybe?) I'm an Android novice and I'm not sure what my options are...
Thanks for helping out!
Alright, as announced, here some example:
If you just use a custom view, you have to keep lists of objects and draw them yourself, as opposed to a custom layout where you just have to measure and layout the children. Since you can just add a button, there's no need to use hit-tests or whatsoever, since if you don't mess up the view will just receive the onClick() call.
Also, you can easily preview your layout in the editor if you correctly implement layout parameters. Which makes development much faster.
E.g. you can define your own layout parameters
<resources>
<declare-styleable name="TimeLineLayout_Layout">
<attr name="time_from" format="string"/>
<attr name="time_to" format="string"/>
</declare-styleable>
</resources>
Then use them like this...
<com.github.bleeding182.timelinelayout.TimeLineLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#22662222">
<TextView
android:layout_width="80dp"
android:layout_height="wrap_content"
android:background="#android:color/holo_green_dark"
android:padding="8dp"
android:text="12:00 - 16:00"
app:time_from="12:00"
app:time_to="16:00"/>
</com.github.bleeding182.timelinelayout.TimeLineLayout>
And the result would look something like this (I know it's ugly, but I made this just for testing :/ )
To do this, you create a basic layout where you measure and layout the views. You can then add any views to your layout, and by setting a time from / to and correctly measuring / layouting you can easily display all sorts of items.
The code for the screenshot is attached below, onDraw will create those ugly hour/half hour lines. onMeasure is for calculating view heights and onLayout is drawing the views to their correct time slot.
I hope this helps, it's sure easier to use than handling everything in one view.
public class TimeLineLayout extends ViewGroup {
private int tIntervalSpan = 24 * 60;
private float mMeasuredMinuteHeight;
public TimeLineLayout(Context context) {
super(context);
}
public TimeLineLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TimeLineLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TimeLineLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams instanceof LayoutParams) {
LayoutParams params = (LayoutParams) layoutParams;
final int top = (int) (params.tFrom * mMeasuredMinuteHeight);
child.layout(l, top, child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
mMeasuredMinuteHeight = getMeasuredHeight() / (float) tIntervalSpan;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams instanceof LayoutParams) {
LayoutParams params = (LayoutParams) layoutParams;
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec((int) ((params.tTo - params.tFrom) * mMeasuredMinuteHeight), MeasureSpec.EXACTLY));
}
}
}
#Override
protected void onDraw(Canvas canvas) {
final float height = mMeasuredMinuteHeight * 60;
Paint paint = new Paint();
paint.setColor(Color.RED);
for(int i = 0; i < 24; i++) {
paint.setStrokeWidth(2f);
paint.setAlpha(255);
canvas.drawLine(0, i * height, getMeasuredWidth(), i*height, paint);
if(i < 23) {
paint.setStrokeWidth(1f);
paint.setAlpha(50);
canvas.drawLine(0, i * height + 30 * mMeasuredMinuteHeight, getMeasuredWidth(), i * height + 30 * mMeasuredMinuteHeight, paint);
}
}
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends ViewGroup.LayoutParams {
private final int tFrom;
private final int tTo;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.TimeLineLayout_Layout);
final String from = a.getString(R.styleable.TimeLineLayout_Layout_time_from);
final String to = a.getString(R.styleable.TimeLineLayout_Layout_time_to);
a.recycle();
tFrom = Integer.parseInt(from.split(":")[0]) * 60 + Integer.parseInt(from.split(":")[1]);
tTo = Integer.parseInt(to.split(":")[0]) * 60 + Integer.parseInt(to.split(":")[1]);
}
}
Just for extending CheckBoxPreference or SwitchPreference on Android Lollipop, the widget (the checkbox or the switch) won't have animation anymore.
I'd like to extend SwitchPreference to force api < 21 to use SwitchCompat instead of the default one they are using (which is obviously wrong).
I am using the new AppCompatPreferenceActivity with appcompat-v7:22.1.1 but that doesn't seem to affect the switches.
The thing is that with just extending those classes, without adding any custom layout or widget resource layout, the animation is gone.
I know I can write two instances of my preference.xml (on inside values-v21) and it will work... But I'd like to know why is this happening and if somebody knows a solution without having two preference.xml.
Code example:
public class SwitchPreference extends android.preference.SwitchPreference {
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SwitchPreference(Context context) {
super(context);
}
}
This or the same for CheckBoxPreference and then using:
<com.my.package.SwitchPreference />
Will make the animation in a Lollipop device to be gone.
--
Another thing I tried for the SwitchPreference (that I can with CheckBoxPreference) is to give a layout with the default id but #android:id/switchWidgetis not public while #android:id/checkbox is. I also know I can use a <CheckBoxPreference /> and give a widget layout that is in fact a SwitchCompat, but I'd like to avoid that (confusing the names).
It seems I found a fix for your issue.
Extensive Explanation
In SwitchCompat, when toggling the the switch, it tests a few functions before playing the animation: getWindowToken() != null && ViewCompat.isLaidOut(this) && isShown().
Full method:
#Override
public void setChecked(boolean checked) {
super.setChecked(checked);
// Calling the super method may result in setChecked() getting called
// recursively with a different value, so load the REAL value...
checked = isChecked();
if (getWindowToken() != null && ViewCompat.isLaidOut(this) && isShown()) {
animateThumbToCheckedState(checked);
} else {
// Immediately move the thumb to the new position.
cancelPositionAnimator();
setThumbPosition(checked ? 1 : 0);
}
}
By using a custom view extending SwitchCompat, I found out, that isShown() always returns false, because the at third iteration of the while, parent == null.
public boolean isShown() {
View current = this;
//noinspection ConstantConditions
do {
if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
ViewParent parent = current.mParent;
if (parent == null) {
return false; // We are not attached to the view root
}
if (!(parent instanceof View)) {
return true;
}
current = (View) parent;
} while (current != null);
return false;
}
Interestingly, the third parent is the second attribute passed to getView(View convertView, ViewGroup parent) in Preference, means the PreferenceGroupAdapter didn't get a parent passed to its own getView(). Why this happens exactly and why this happens only for custom preference classes, I don't know.
For my testing purposes, I used the CheckBoxPreference with a SwitchCompat as widgetLayout, and I also didn't see animations.
Fix
Now to the fix: simply make your own view extending SwitchCompat, and override your isShown() like this:
#Override
public boolean isShown() {
return getVisibility() == VISIBLE;
}
Use this SwitchView for your widgetLayout style, and animations work again :D
Styles:
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
…
<item name="android:checkBoxPreferenceStyle">#style/Preference.SwitchView</item>
…
</style>
<style name="Preference.SwitchView">
<item name="android:widgetLayout">#layout/preference_switch_view</item>
</style>
Widget layout:
<de.Maxr1998.example.preference.SwitchView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="false"
android:focusable="false" />
Sometimes Extending from a Class is not the best solution. To avoid loosing the animations you could instead Compose it, I meant creating a Class where you have a SwitchPreference field variable and apply the new logic to it. It's like a wrapper. This worked for me.
i manage to fix it like this and animations is working before it was going to the state directly without animation:
FIX:
CustomSwitchCompat.class
public class CustomSwitchCompat extends SwitchCompat {
public CustomSwitchCompat(Context context) {
super(context);
}
public CustomSwitchCompat(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean isShown() {
return getVisibility() == VISIBLE;
}
}
In your layout do this: preference_switch_layout.xml
<com.example.CustomSwitchCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="false"
android:focusable="false"
app:switchMinWidth="55dp"/>
and in your preference.xml do this:
<CheckBoxPreference
android:defaultValue="false"
android:key=""
android:widgetLayout="#layout/preference_switch_layout"
android:summary=""
android:title="" />
I was having this issue, when I was using custom layout (app:layout) for SwitchPreference. At first, switch animation was triggered, but after a little scrolling it stopped and switch was jumping without animation. I tried every solution from stackoverflow, but nothing helped.
After debugging of SwitchCompat.setChecked method I found out that this condition is failing:
public void setChecked(boolean checked) {
...
if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
animateThumbToCheckedState(checked);
} else {
// Immediately move the thumb to the new position.
cancelPositionAnimator();
setThumbPosition(checked ? 1 : 0);
}
}
Concretely ViewCompat.isLaidOut(this) returned false. I guess this is a bug either in View or Preference (or subclasses). Anyway, I was able to fix this with little hack.
I created a subclass of SwitchCompat and did override setChecked method, where I call requestLayout() and in onNextLayout I call SwitchCompat's setChecked method. This guarantees that isLaidOut condition is true when changing checked state.
Full code of custom SwitchCompat:
class SwitchCompatFix #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = androidx.appcompat.R.attr.switchStyle,
): SwitchCompat(context, attrs, defStyleAttr) {
override fun setChecked(checked: Boolean) {
doOnNextLayout {
post { super.setChecked(checked) }
}
requestLayout()
}
}
public class SwitchPreference extends android.preference.SwitchPreference {
public SwitchPreference(Context context) {
this(context, null);
}
public SwitchPreference(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkBoxPreferenceStyle);
}
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
try {
Field canRecycleLayoutField = Preference.class.getDeclaredField("mCanRecycleLayout");
canRecycleLayoutField.setAccessible(true);
canRecycleLayoutField.setBoolean(this, true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
I am trying to create ViewGroup based on FrameLayout that might be rotated 90 degrees CW / CCW and it still will be working correctly
So far my results are not so sucesful. So far it looks like that ( left side before rotation, right after; sorry for bright red )
Layout for Activity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.TestProject.RotatedFrameLayout
android:id="#+id/container"
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00F"/>
</RelativeLayout>
RotatedFrameLayout
public class RotatedFrameLayout extends FrameLayout {
private boolean firstMeasure = true;
public RotatedFrameLayout( Context context ) {
super( context );
init();
}
public RotatedFrameLayout( Context context, AttributeSet attrs ) {
super( context, attrs );
init();
}
public RotatedFrameLayout( Context context, AttributeSet attrs, int defStyle ) {
super( context, attrs, defStyle );
init();
}
private void init() {
setRotation( 90f );
}
#Override
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) {
super.onMeasure( heightMeasureSpec, widthMeasureSpec );
}
}
Some extra info
I don't want to use Animation rotation because buttons aren't clickable that way
I don't want to use landscape mode because in landscape on screen navigation buttons took a lot of space on Nexus 7 ( this is the main reason why I am trying to greate that rotated
It seems that only left and right side of the screen are out of bounds
It is quite hard to do and I think it is not worth doing. But if you really want to do this you need:
pass to ViewGroup correct size dimentions (swap width and height).
rotate ViewGroup canvas 90 degrees.
At this point everything should look fine, but touch events not working properly.
intercept all touch events and swap x and y. Then pass fixed events to ViewGroup.
I dont have any code samples and have never seen any ) This way should work, we did scale transformations with fragments where we had to fix touch events coordinates when fragment was scaled.
I havent tested it heavily but this works:
public class RotatedFrameLayout extends FrameLayout {
public RotatedFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public RotatedFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RotatedFrameLayout(Context context) {
super(context);
init();
}
#SuppressLint("NewApi")
private void init() {
setPivotX(0);
setPivotY(0);
setRotation(90f);
}
#SuppressLint("NewApi")
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setTranslationX(getMeasuredHeight());
}
}
I want to create a rotating progress image, and wonder what's the best way to proceed. I can make it work with an animation list with for example 12 images changing every 100ms. This works fine, but it's quite tedious to create 12 images or for every size and resolution:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="#drawable/ic_loading_grey_on_black_01" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_02" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_03" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_04" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_05" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_06" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_07" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_08" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_09" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_10" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_11" android:duration="100" />
<item android:drawable="#drawable/ic_loading_grey_on_black_12" android:duration="100" />
I suppose that an easier solution is to use one image per resolution, but rather rotate it for each frame. In the platform resources (android-sdk-windows/platforms...) I found something called animated-rotate in the file drawable/search_spinner.xml, but if I copy the code get a compiler error complaining about android:framesCount and android:frameDuration (Google APIs 2.2 in Eclipse):
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/spinner_black_20"
android:pivotX="50%"
android:pivotY="50%"
android:framesCount="12"
android:frameDuration="100" />
I have also tried using a repeating rotate animation (using in the anim resource folder), but I actually prefer the look of the animation list version.
What is the recommended way of solving this problem?
Rotate drawable suggested by Praveen won't give you control of frame count. Let's assume you want to implement a custom loader which consists from 8 sections:
Using animation-list approach, you need to create 8 frames rotated by 45*frameNumber degrees manually. Alternatively, you can use 1st frame and set rotation animation to it:
File res/anim/progress_anim.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite" />
File MainActivity.java
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(1000);
imageView.startAnimation(a);
This will give you smooth animation instead of 8-stepped. To fix this we need to implement custom interpolator:
a.setInterpolator(new Interpolator() {
private final int frameCount = 8;
#Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
Also you can create a custom widget:
File res/values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressView">
<attr name="frameCount" format="integer"/>
<attr name="duration" format="integer" />
</declare-styleable>
</resources>
File ProgressView.java:
public class ProgressView extends ImageView {
public ProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAnimation(attrs);
}
public ProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
setAnimation(attrs);
}
public ProgressView(Context context) {
super(context);
}
private void setAnimation(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressView);
int frameCount = a.getInt(R.styleable.ProgressView_frameCount, 12);
int duration = a.getInt(R.styleable.ProgressView_duration, 1000);
a.recycle();
setAnimation(frameCount, duration);
}
public void setAnimation(final int frameCount, final int duration) {
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(duration);
a.setInterpolator(new Interpolator() {
#Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
startAnimation(a);
}
}
File activity_main.xml:
<com.example.widget.ProgressView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_progress"
app:frameCount="8"
app:duration="1000"/>
File res/anim/progress_anim.xml: listed above
You have to create a drawable xml file like below:
Code:
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0"
android:toDegrees="360" android:drawable="#drawable/imagefile_to_rotate" />
I found vokilam's answer to be the best one to create a nice stepped/staggered animation. I went for his final suggestion and made a custom widget, the only problem I encountered was that setting visibility wouldn't work because it was animated and thus would always be visible...
I adjusted his code (ProgressView.java which I renamed StaggeredProgress.java) like this:
public class StaggeredProgress extends ImageView {
private Animation staggered;
public StaggeredProgress(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAnimation(attrs);
}
public StaggeredProgress(Context context, AttributeSet attrs) {
super(context, attrs);
setAnimation(attrs);
}
public StaggeredProgress(Context context) {
super(context);
}
private void setAnimation(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StaggeredProgress);
int frameCount = a.getInt(R.styleable.StaggeredProgress_frameCount, 12);
int duration = a.getInt(R.styleable.StaggeredProgress_duration, 1000);
a.recycle();
setAnimation(frameCount, duration);
}
public void setAnimation(final int frameCount, final int duration) {
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(duration);
a.setInterpolator(new Interpolator() {
#Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
staggered = a;
//startAnimation(a);
}
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if( visibility == View.VISIBLE )
startAnimation(staggered);
else
clearAnimation();
}
}
This way setting the view's visibility starts and stops the animation as required...Many thanks again to vokilam!
see examples here
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/index.html
specifically:
Progress Bar
Incremental
Demonstrates large and small rotating progress indicators that can be incremented or decremented in units.
Smooth
Demonstrates large and small continuously rotating progress indicators used to indicate a generic "busy" message.
Dialogs
Demonstrates a ProgressDialog, a popup dialog that hosts a progress bar. This example demonstrates both determinate and indeterminate progress indicators.
In Title Bar
Demonstrates an Activity screen with a progress indicator loaded by setting the WindowPolicy's progress indicator feature.
SACPK's solution definitely works. Another solution can be to use <animated-rotate> just like in question and remove android:framesCount="12"
android:frameDuration="100" attributes for those the compiler complains. It still works even for my 8-frame image.
However, I havn't figured out how to control the speed of the animation :(.
Thank #vokilam. This similar solution (a custom view that rotates automatically) uses <animation-list> dynamically in its implementation:
public class FramesAnimatorView extends AppCompatImageView {
private int framesCount;
private int duration;
private Bitmap frameBitmap;
public FramesAnimatorView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public FramesAnimatorView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FramesAnimatorView(Context context) { super(context); }
private void init(Context context, AttributeSet attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FramesAnimatorView);
framesCount = typedArray.getInt(R.styleable.FramesAnimatorView_framesCount, 12);
duration = typedArray.getInt(R.styleable.FramesAnimatorView_duration, 1200);
typedArray.recycle();
// Method 1: Use <rotate> as Animation (RotateAnimation) and startAnimation() (Rotate view itself).
//method1(framesCount, duration);
// Method 2: Use <rotate> as Drawable (RotateDrawable) and ObjectAnimator. Usable for API 21+ (because of using RotateDrawable.setDrawable).
//method2();
// Method 3 (Recommended): Use <animation-list> (AnimationDrawable) dynamically.
final int frameDuration = this.duration / framesCount;
final AnimationDrawable animationDrawable = (AnimationDrawable) getDrawable();
for (int i = 0; i < framesCount; i++)
animationDrawable.addFrame(
new RotatedDrawable(frameBitmap, i * 360f / framesCount, getResources()),
frameDuration);
animationDrawable.start();
}
#Override public void setImageResource(int resId) { //info();
frameBitmap = BitmapFactory.decodeResource(getResources(), resId);
super.setImageDrawable(new AnimationDrawable());
}
#Override public void setImageDrawable(#Nullable Drawable drawable) { //info();
frameBitmap = drawableToBitmap(drawable);
super.setImageDrawable(new AnimationDrawable());
}
#Override public void setImageBitmap(Bitmap bitmap) { //info();
frameBitmap = bitmap;
super.setImageDrawable(new AnimationDrawable());
}
/**
* See #android-developer's answer on stackoverflow.com.
*/
private static class RotatedDrawable extends BitmapDrawable {
private final float degrees;
private int pivotX;
private int pivotY;
RotatedDrawable(Bitmap bitmap, float degrees, Resources res) {
super(res, bitmap);
pivotX = bitmap.getWidth() / 2;
pivotY = bitmap.getHeight() / 2;
this.degrees = degrees;
}
#Override public void draw(final Canvas canvas) {
canvas.save();
canvas.rotate(degrees, pivotX, pivotY);
super.draw(canvas);
canvas.restore();
}
}
/**
* See #André's answer on stackoverflow.com.
*/
#NonNull private static Bitmap drawableToBitmap(Drawable drawable) {
final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
See Android-FramesAnimatorView on GitHub for full (and probably more updated) source code.