Can LayoutAnimationController animate only specified Views - android

Is it possible to override Android's LayoutAnimationController in such a way that only certain child Views that I specify inside a ViewGroup will animate? My goal is to choose an arbitrary set of the child views based on the current state of the Activity and animate them with the same animation at exactly the same time, then at a later time choose a different arbitrary set of child views and do the same thing. I would like to be able to do this continually until the Activity is finished running.
So far I have looked at and dismissed a couple of options:
Calling startAnimation(Animation) the specific child views
individually, however there is not a guarantee that they will all
start and end at exactly the same time, especially if the number of
views in the arbitrary set is large.
Overriding LayoutAnimationController.getAnimationForView() seemed
like it would be the easiest way, but the method is final and cannot
be overridden.
I have been scratching my head for some time on this and figured I would give Stack Overflow a shot.

I wasn't able to find a way to change Android's LayoutAnimationController, but did come up with a simple solution that does what I want. I created a View subclass that can selectively ignore calls to run an animation if I so choose.
public class AnimationAverseView extends View {
private boolean mIsAnimatable = true;
public void setAnimatible(boolean isAnimatable) {
if (!isAnimatable) {
clearAnimation();
}
mIsAnimatable = isAnimatable;
}
#Override
public void startAnimation(Animation animation) {
if (mIsAnimatable) {
super.startAnimation(animation);
}
}
}
I did not worry about any other possible animation-related methods, since I only animate the Views via a LayoutAnimationController.

I see this question was asked 7 years and 6 months ago, but today I have encountered it myself and creating custom child views seems a bit cumbersome to me.
I found that using a custom outer layout seems to do the trick, where you have to override the attachLayoutAnimationParameters. This method is called for every child view that is part of a layout animation, and it is called when the layout animation is actually triggered within the ViewGroup. It is attaching AnimationLayoutParams to the child, in which case it is animated. So simply said, if you do not attach any LayoutAnimation Parameters to a child, it will not be part of the layout animation.
class CustomLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val excludeForLayoutAnimation by lazy {
listOfNotNull(findViewById(R.id.text), findViewById(R.id.fab))
}
override fun attachLayoutAnimationParameters(
child: View?,
params: ViewGroup.LayoutParams?,
index: Int,
count: Int
) {
if (child !in excludeForLayoutAnimation) {
super.attachLayoutAnimationParameters(child, params, index, count)
}
}
}
If you now simply call this on your outer custom view, it will exclude the children that you specify:
customView?.apply {
layoutAnimation = AnimationUtils.loadLayoutAnimation(
context,
R.anim.layout_animation_fade_in
)
scheduleLayoutAnimation() // or maybe startLayoutAnimation()
}

I cannot add a comment to the accepted answer, so adding some additional information here. The comment from happydude (Feb 17) was correct for me as well. I had a FrameLayout as the outermost View for my ListView header, and I needed to override the setAnimation() method to prevent the header from being animated with the rest of the list. So you should check out which other animation methods you might need to override.

Related

Pass external parameter to Custom View

I just get going on drawing, canvas, basic animations but have stumbled upon this annoying issue:
I have a CustomView
public class CustomView extends View{
//
//Edited code ****************************
bool dir; // true -- right-to-left, false -- left-to-right
public void setDirection(bool b)
this.bool = b
// ****************************************
public CustomView(Context context) {
super(context);
...
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs)
...
}
...
//stuff for animation
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
...
}
...
and inside I have created a little animation, basically something flying from right to left over and over again.
Now, let's say I wanted to have 2 of these views in my layout. But on the second one, stuff should fly from left to right.
Is it possible to somehow pass this "parameter" to the Custom view? or do I really have to create the exact same class and change a plus sign to a minus sign and make it thereby a new class. This would mean animations created by extending view are not tunable at all.
If the latter is the case, then is there a better way to have tunable animations?
Is it possible to somehow pass this "parameter" to the Custom view?
Step #1: Add a field to your custom view to hold your animation
Step #2: Add a setter method to populate the field
Step #3: Call that setter method from something (activity, fragment, etc.) to tell it what animation to use

onGlobalLayout differentiate between various invocations

I have a logo view, which is a full screen fragment containing single ImageView.
I have to perform some operations after the logo image is completely visible.
Following code is used to invoke the special task
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ImageView logoImageMaster = new ImageView(getContext());
//logoImageMaster.setImageResource(resID); //even after removing this, i am getting the callback twice
try {
// get input stream
InputStream ims = getActivity().getAssets().open("product_logo.png");
// load image as Drawable
Drawable d = Drawable.createFromStream(ims, null);
// set image to ImageView
logoImageMaster.setImageDrawable(d);
}
catch(IOException ex) {
}
logoImageMaster.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() { //FIXME get called twice. Check this out, no info to distinguish first from second
// Log.e("PANEL", "onGlobalLayout of Logo IV ---------------------------------");
activityInterface.doSpecialLogic();
}
});
return logoImageMaster;
}
My exact problem is, onGlobalLayout is called twice for this view hierarchy.
I know that onGlobalLayout is invoked in performTraversal of View.java hence this is expected.
For my use case of Single parent with Single child view, I want to distinguish the view attributes such that doSpecialLogic is called once[onGlobalLayout is called twice] , after the logo image is completely made visible.
Please suggest some ideas.
OnGlobalLayoutListener gets called every time the view layout or visibility changes. Maybe you reset the views in your doSpecialLogic call??
edit
as #Guille89 pointed out, the two set calls cause onGlobalLayout to be called two times
Anyhow, if you want to call OnGlobalLayoutListener just once and don't need it for anything else, how about removing it after doSpecialLogic() call??
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
//noinspection deprecation
logoImageMaster.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
logoImageMaster.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
activityInterface.doSpecialLogic();
It seems to be called one time for each set done over the imageView
logoImageMaster.setImageResource(resID);
logoImageMaster.setImageDrawable(d);
You should Try using kotlin plugin in android
This layout listener is usually used to do something after a view is measured, so you typically would need to wait until width and height are greater than 0. And we probably want to do something with the view that called it,in your case
Imageview
So generified the function so that it can be used by any object that extends View and also be able to access to all its specific functions and properties from the function
[kotlin]
inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
[/kotlin]
Note:
make sure that ImageView is described properly in the layout. That is its layout_width and layout_height must not be wrap_content. Moreover, other views must not result in this ImageView has 0 size.

Requirements for a custom android View

I'm interested in using a custom View to draw, measure, and display a set of buttons that is dependent on a back-end for my application. This requires me to implement this in Android dynamically. Would you help me get started?
Here we go: first in my MainActivity I instantiate my custom class which inherits from TableLayout which is also a view:
var keyboardView = new KeyboardView(this, layout, droidLayout, this.Colors);
Then I set the content view to the fresh instance of my custom class: SetContentView(keyboardView); Here's my class's constructor which just helps me get scope on all of the info I need:
public KeyboardView(Context context, KeyboardLayout layout, int droidLayout, Dictionary<string, int> colors)
: base(context) {
this._Colors = colors;
this._Context = context;
this.KeyboardLayout = layout;
this.SetWillNotDraw(false);
//this.ButtonLayout = ll;
this.DrawingCacheEnabled = true;
this.DroidLayout = droidLayout;
I've also overridden both OnMeasure and OnDraw:
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int h = 100;
int w = 100;
//Overall keyboard/View dimensions //Difference between Canvas size and KeyboardDimensions?
this.SetMeasuredDimension(w, h);
//this.Layout.CanvasSize.
}
protected override void OnDraw(Canvas canvas) {
var bt = new Button(this._Context);
bt.Text = "laaaa";
this.AddView(bt);
}
Now what's happening is that OnDraw and OnMeasure both get it, in fact, OnDraw seems to be getting hit lots of time -- more so then I wish to count. However, the one button that I added via AddView is NOT drawn on the screen. If you guys could help me get this one button on the screen I can get to writing the core logic!
On a side-note: I can draw stuff on my screen if I set an XML layout file as the view as such: SetContentView(Resource.Layout.LayoutName) But, since the nature of my program requires dynamic views being added all the time, I'd rather avoid writing lengthy Layout files. Thanks guys! Bump my question up if you think it's a worthwhile one!
In general, this is a good place to start. In particular it details how to add custom attributes, how to perform custom drawing, and how to design custom events that make sense in the context of your own application.
Also, it looks like you're trying to use C# style syntax in Java, which won't work for things like inheritance. Reading some java tutorials might help you out.
This is a good resource for that and should help you get up and going. Good luck!
Don't forget to call the super() to allow the parent class to do what it needs to do for the overridden method.
protected override void OnDraw(Canvas canvas) {
super(canvas)
}
Related link
http://developer.android.com/guide/topics/ui/custom-components.html#compound

Android 4.4 — Translucent status/navigation bars — fitsSystemWindows/clipToPadding don't work through fragment transactions

When using the translucent status and navigation bars from the new Android 4.4 KitKat APIs, setting fitsSystemWindows="true" and clipToPadding="false" to a ListView works initially. fitsSystemWindows="true" keeps the list under the action bar and above the navigation bar, clipToPadding="false" allows the list to scroll under the transparent navigation bar and makes the last item in the list scroll up just far enough to pass the navigation bar.
However, when you replace the content with another Fragment through a FragmentTransaction the effect of fitsSystemWindows goes away and the fragment goes under the action bar and navigation bar.
I have a codebase of demo source code here along with a downloadable APK as an example: https://github.com/afollestad/kitkat-transparency-demo. To see what I'm talking about, open the demo app from a device running KitKat, tap an item in the list (which will open another activity), and tap an item in the new activity that opens. The fragment that replaces the content goes under the action bar and clipToPadding doesn't work correctly (the navigation bar covers the last item in the list when you scroll all the way down).
Any ideas? Any clarification needed? I posted the before and after screenshots of my personal app being developed for my employer.
I struggled with the same problem yesterday. After thinking a lot, I found an elegant solution to this problem.
First, I saw the method requestFitSystemWindows() on ViewParent and I tried to call it in the fragment's onActivityCreated() (after the Fragment is attached to the view hierarchy) but sadly it had no effect. I would like to see a concrete example of how to use that method.
Then I found a neat workaround: I created a custom FitsSystemWindowsFrameLayout that I use as a fragment container in my layouts, as a drop-in replacement for a classic FrameLayout. What it does is memorizing the window insets when fitSystemWindows() is called by the system, then it propagates the call again to its child layout (the fragment layout) as soon as the fragment is added/attached.
Here's the full code:
public class FitsSystemWindowsFrameLayout extends FrameLayout {
private Rect windowInsets = new Rect();
private Rect tempInsets = new Rect();
public FitsSystemWindowsFrameLayout(Context context) {
super(context);
}
public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FitsSystemWindowsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected boolean fitSystemWindows(Rect insets) {
windowInsets.set(insets);
super.fitSystemWindows(insets);
return false;
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
tempInsets.set(windowInsets);
super.fitSystemWindows(tempInsets);
}
}
I think this is much simpler and more robust than hacks that try to determine the UI elements sizes by accessing hidden system properties which may vary over time and then manually apply padding to the elements.
I solved the issue by using the library I use the set the color of my translucent status bar.
The SystemBarConfig class of SystemBarTint (as seen here https://github.com/jgilfelt/SystemBarTint#systembarconfig) lets you get insets which I set as the padding to the list in every fragment, along with the use of clipToPadding="false" on the list.
I have details of what I've done on this post: http://mindofaandroiddev.wordpress.com/2013/12/28/making-the-status-bar-and-navigation-bar-transparent-with-a-listview-on-android-4-4-kitkat/
Okay, so this is incredibly weird. I just recently ran into this same issue except mine involves soft keyboard. It initially works but if I add fragment transaction, the android:fitsSystemWindows="true" no longer works. I tried all the solution here, none of them worked for me.
Here is my problem:
Instead of re-sizing my view, it pushes up my view and that is the problem.
However, I was lucky and accidentally stumbled into an answer that worked for me!
So here it is:
First of all, my app theme is: Theme.AppCompat.Light.NoActionBar (if that is relevant, maybe it is, android is weird).
Maurycy pointed something very interesting here, so I wanted to test what he said was true or not. What he said was true in my case as well...UNLESS you add this attribute to your activity in the android manifest of your app:
Once you add:
android:windowSoftInputMode="adjustResize"
to your activity, android:fitsSystemWindows="true" is no longer ignored after the fragment transaction!
However, I prefer you calling android:fitsSystemWindows="true" NOT on the root layout of your Fragment. One of the biggest places where this problem will occur is where if you have EditText or a ListView. If you are stuck in this predicament like I did, set android:fitsSystemWindows="true" in the child of the root layout like this:
YES, this solution works on all Lollipop and pre-lollipop devices.
And here is the proof:
It re-sizes instead of pushing the layout upwards.
So hopefully, I have helped someone who is on the same boat as me.
Thank you all very much!
A heads up for some people running into this problem.
A key piece of information with fitSystemWindows method which does a lot of the work:
This function's traversal down the hierarchy is depth-first. The same
content insets object is propagated down the hierarchy, so any changes
made to it will be seen by all following views (including potentially
ones above in the hierarchy since this is a depth-first traversal).
The first view that returns true will abort the entire traversal.
So if you have any other fragments with content views which have fitsSystemWindows set to true the flag will potentially be ignored. I would consider making your fragment container contain the fitsSystemWindows flag if possible. Otherwise manually add padding.
I've been struggling quite a bit with this as well.
I've seen all the responses here. Unfortunately none of them was fixing my problem 100% of the time.
The SystemBarConfig is not working always since it fails to detect the bar on some devices.
I gave a look at the source code and found where the insets are stored inside the window.
Rect insets = new Rect();
Window window = getActivity().getWindow();
try {
Class clazz = Class.forName("com.android.internal.policy.impl.PhoneWindow");
Field field = clazz.getDeclaredField("mDecor");
field.setAccessible(true);
Object decorView = field.get(window);
Field insetsField = decorView.getClass().getDeclaredField("mFrameOffsets");
insetsField.setAccessible(true);
insets = (Rect) insetsField.get(decorView);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
This is how to get them.
Apparently in Android L there'll be a nice method to get those insets but in the meantime this might be a good solution.
I encountered the same problem. When I replace Fragment.
The 'fitsSystemWindows' doesn't work.
I fixed by code add to your fragment
#Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
AndroidUtil.runOnUIThread(new Runnable() {
#Override
public void run() {
((ViewGroup) getView().getParent()).setFitsSystemWindows(true);
}
});
}
Combined with #BladeCoder answer i've created FittedFrameLayout class which does two things:
it doesn't add padding for itself
it scan through all views inside its container and add padding for them, but stops on the lowest layer (if fitssystemwindows flag is found it won't scan child deeper, but still on same depth or below).
public class FittedFrameLayout extends FrameLayout {
private Rect insets = new Rect();
public FittedFrameLayout(Context context) {
super(context);
}
public FittedFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FittedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
protected void setChildPadding(View view, Rect insets){
if(!(view instanceof ViewGroup))
return;
ViewGroup parent = (ViewGroup) view;
if (parent instanceof FittedFrameLayout)
((FittedFrameLayout)parent).fitSystemWindows(insets);
else{
if( ViewCompat.getFitsSystemWindows(parent))
parent.setPadding(insets.left,insets.top,insets.right,insets.bottom);
else{
for (int i = 0, z = parent.getChildCount(); i < z; i++)
setChildPadding(parent.getChildAt(i), insets);
}
}
}
#Override
protected boolean fitSystemWindows(Rect insets) {
this.insets = insets;
for (int i = 0, z = getChildCount(); i < z; i++)
setChildPadding(getChildAt(i), insets);
return true;
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
setChildPadding(child, insets);
}
}
I have resolve this question in 4.4
if(test){
Log.d(TAG, "fit true ");
relativeLayout.setFitsSystemWindows(true);
relativeLayout.requestFitSystemWindows();
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}else {
Log.d(TAG, "fit false");
relativeLayout.setFitsSystemWindows(false);
relativeLayout.requestFitSystemWindows();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}

"requestLayout() improperly called by..." error on Android 4.3

I have a simple Custom TextView that sets custom font in its constructor like the code below
public class MyTextView extends TextView {
#Inject CustomTypeface customTypeface;
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
RoboGuice.injectMembers(context, this);
setTypeface(customTypeface.getTypeface(context, attrs));
setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
}
}
It works fine from Gingerbread through JB 4.2. But the adb logcat is flooded with the following messages when I show my custom textview on Android 4.3 phone.
10-05 16:09:15.225: WARN/View(9864): requestLayout() improperly called by com.cmp.views.MyTextView{42441b00 V.ED.... ......ID 18,218-456,270 #7f060085 app:id/summary} during layout: running second layout pass
10-05 16:09:15.225: WARN/View(9864): requestLayout() improperly called by com.cmp.views.MyTextView{423753d0 V.ED.... ......ID 26,176-742,278 #7f060085 app:id/summary} during layout: running second layout pass
I notice, it does slow down UI a bit. Any ideas why it's happening on 4.3?
Appreciate your help.
I found where this bug occurs in my app. Although occurrence of this is not found in the code you provided (it may help if you have done this elsewhere in your code), it will hopefully help others fix this impossible-to-trace problem.
I had a line (not added by me, of course):
myView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//this would then make a call to update another view's layout.
}
});
In my app, I did not need any listener, so removing this entire block fixed this problem. For those that need something like this, remember to remove the listener after the layout has changed (inside of this callback).
Looking into the Android source, this problem is described in a little more detail:
requestLayout() was called during layout. If no layout-request flags are set on the requesting views, there is no problem. If some requests are still pending, then we need to clear those flags and do a full request/measure/layout pass to handle this situation.
It appears that the problem may be related to Roboguice; see issue #88. The suggested solution there is to use #InjectView:
You can now use #InjectView from inside a view class. Just call Injector.injectMembers() after you've populated your view, ala:
public class InjectedView extends FrameLayout {
#InjectView(R.id.view1) View v;
public InjectedView(Context context) {
super(context);
final View child = new View(context);
child.setId(R.id.view1);
addView(child);
RoboGuice.getInjector(context).injectMembers(this);
}
}
Perhaps you should consider migrating RoboGuice.injectMembers(context, this) to the declaration of your View object using the #InjectView annotation.
I fixed these warnings in my custom ListView item (LinearLayout subclass). This class implements Checkable, and has a setChecked(boolean checked) method that is called to indicate whether the item is checked:
#Override
public void setChecked(boolean checked) {
mChecked = checked;
TextView textView = (TextView)this.findViewById(R.id.drawer_list_item_title_text_view);
if(mChecked) {
textView.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Bold.ttf"));
}
else {
textView.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Regular.ttf"));
}
}
I visually indicate the checked state by calling setTypeFace() on a textView in my view, toggling between regular and bold typefaces. These setTypeFace() calls were causing the warnings.
To fix the problem, I created instance variables for the Typefaces in the class constructor and used them later, when changing the typeface, rather than calling Typeface.createFromAsset(...) every time:
private Typeface mBoldTypeface;
private Typeface mRegularTypeface;
public DrawerView(Context context, AttributeSet attrs) {
super(context, attrs);
initTypefaces();
}
private void initTypefaces() {
this.mBoldTypeface = Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Bold.ttf");
this.mRegularTypeface = Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Regular.ttf");
}
#Override
public void setChecked(boolean checked) {
mChecked = checked;
TextView textView = (TextView)this.findViewById(R.id.drawer_list_item_title_text_view);
if(mChecked) {
textView.setTypeface(mBoldTypeface);
}
else {
textView.setTypeface(mRegularTypeface);
}
}
This is a pretty specific scenario, but I was pleasantly surprised to find a fix and maybe someone else is in the same situation.
Please check weather the Id of any view is repeating inside the same activity context. I was also getting the same warning, I was using a TextView repeatedly a loop with same id. I resolved the problem by using different ids each time.
I met the same problem. That's because I was trying to set a view's position in the iOS way. You know in iOS set a view's position by set the view's left top value and width, height. But in Android, it should be (left, top, right, bottom). I did pay much attention on this. When I jump into the layout() definition, I read the method's comment, then I find out why the warning happened.

Categories

Resources