I am struggling for hours with some problem I encountered while creating a simple animation in my app.
I have a LinearLayout with elements inside that is hidden behind an Image View and which should be shown (by animating on layouts width) on the click event. If I delete elements from inside the LinearLayout everything performs well, but because the children of the layout are pushing its width (which means that even if layouts width is set to 0 it is still pushed to width +/- 100dp by the elements inside) I have problems with hiding the layout.
However it is not the case when animation is performed - when I am animating layout to go to width 0 it properly crops the children. But as soon as animation is done - the layout is pushed by the children. I had an idea to make layout crop its children by default, but I have tried multiple solutions and none of them worked. Then I thought that maybe I will just hide elements (visibility gone) after animation is done. And it "kinda" solved the problem - but I am still having a problem with first occurrence of "showing" animation.
What I am doing is - at the start I am setting layouts width to 0 and hiding it along with its children (setting their to visibility gone). Then on "showing" animation I am making layout and its children visible in onAnimationStart method and then the animation increases layouts width from 0 to 156dp. "Hiding" animation is reverted - I am decreasing layouts width to 0 and onAnimationEnd I am hiding the layout and its content. The problem is that for some reason the first time showing animation is invoked and I am making the layout and views visible in onAnimationStart, there is a gap between this code and the moment which animation actually starts, which makes views visible for a split second before the animation starts.
This is how it looks:
hidden, shown. And here is the xml code for it:
<LinearLayout
android:id="#+id/linSettingsPopup"
android:layout_width="156dp"
android:layout_height="56dp"
app:layout_constraintTop_toTopOf="#id/imgProfilePicture"
app:layout_constraintBottom_toBottomOf="#id/imgProfilePicture"
app:layout_constraintRight_toRightOf="#id/imgProfilePicture"
android:layout_marginEnd="24dp"
android:background="#drawable/settings_popup_container"
android:orientation="horizontal"
android:gravity="center|start"
android:visibility="visible">
<ImageButton
android:id="#+id/btnLogout"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="22dp"
android:background="#drawable/logout"
android:visibility="visible">
</ImageButton>
<ImageButton
android:id="#+id/btnSettings"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="#drawable/settings"
android:visibility="visible">
</ImageButton>
</LinearLayout>
To better understand what is happening, here is the code for everything.
Setting up the view during onCreate method (of course, views are assigned to variables at this point):
private void initializeSettingsPopup()
{
settingsPopupWidth = linSettingsPopup.getLayoutParams().width;
linSettingsPopup.getLayoutParams().width = 0;
linSettingsPopup.requestLayout();
btnLogout.setVisibility(View.GONE);
btnSettings.setVisibility(View.GONE);
linSettingsPopup.setVisibility(View.GONE);
}
Animation:
public class ResizeWidthAnimation extends Animation {
private int mWidth;
private int mStartWidth;
private View mView;
public ResizeWidthAnimation(View view, int width) {
mView = view;
mWidth = width;
mStartWidth = view.getWidth();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newWidth = mStartWidth + (int) ((mWidth - mStartWidth) * interpolatedTime);
mView.getLayoutParams().width = newWidth;
mView.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
Invoking an animation:
private void showSettingsPopup()
{
linSettingsPopup.setVisibility(View.INVISIBLE);
settingsPopupShown = true;
ResizeWidthAnimation resizeAnimation = new ResizeWidthAnimation(linSettingsPopup, settingsPopupWidth);
resizeAnimation.setDuration(350);
resizeAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
linSettingsPopup.setVisibility(View.VISIBLE);
btnLogout.setVisibility(View.VISIBLE);
btnSettings.setVisibility(View.VISIBLE);
}
});
linSettingsPopup.startAnimation(resizeAnimation);
}
private void hideSettingsPopup()
{
settingsPopupShown = false;
ResizeWidthAnimation resizeAnimation = new ResizeWidthAnimation(linSettingsPopup, 0);
resizeAnimation.setDuration(350);
resizeAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
linSettingsPopup.setVisibility(View.GONE);
btnLogout.setVisibility(View.GONE);
btnSettings.setVisibility(View.GONE);
}
});
linSettingsPopup.startAnimation(resizeAnimation);
}
I will also add that I managed to solve this problem by delaying a bit the code inside onAnimationStart method of "showing" animation. However it feels more like avoiding the problem than solving it, so I hope somebody can point out what I am doing wrong and have some good idea how I can solve this.
After a while I finally found a "solution" that does not require any operations on views visibility. It turns out that if layout width is 0dp, the views inside will keep pushing it, which makes layout be as wide as required to fit its children. But if I set the layout width to 1dp it actually cuts the views inside of it. So instead of animating layouts width from 156dp to 0dp on hide, I animate it from 156dp to 1dp and it works fine without any problems. Here is the working code for it:
private static final int SETTINGS_POPUP_ANIM_DURATION = 350;
private static final int SETTINGS_POPUP_HIDDEN_WIDTH = 1;
private void initializeSettingsPopup()
{
settingsPopupWidth = linSettingsPopup.getLayoutParams().width;
linSettingsPopup.getLayoutParams().width = SETTINGS_POPUP_HIDDEN_WIDTH;
linSettingsPopup.requestLayout();
}
private void showSettingsPopup()
{
settingsPopupShown = true;
ResizeWidthAnimation resizeAnimation = new ResizeWidthAnimation(linSettingsPopup, settingsPopupWidth);
resizeAnimation.setDuration(SETTINGS_POPUP_ANIM_DURATION);
linSettingsPopup.startAnimation(resizeAnimation);
}
private void hideSettingsPopup()
{
settingsPopupShown = false;
ResizeWidthAnimation resizeAnimation = new ResizeWidthAnimation(linSettingsPopup, SETTINGS_POPUP_HIDDEN_WIDTH);
resizeAnimation.setDuration(SETTINGS_POPUP_ANIM_DURATION);
linSettingsPopup.startAnimation(resizeAnimation);
}
Related
I have a kids drawing app, but the gestures in Q keep quitting the app. I tried removing the system gesture but it does not seem to work.
In this case, I am trying to exclude the whole screen from system gesture:
List<Rect> exclusionRects = new ArrayList();
public void onLayout(boolean changedCanvas, int left, int top, int right, int bottom) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
exclusionRects.clear();
exclusionRects.add(new Rect(left, top, right, bottom));
setSystemGestureExclusionRects(exclusionRects);
}
}
As stated by Google:
First, to ensure reliable and consistent operation, there’s a 200dp vertical app exclusion limit for the Back gesture.
https://android-developers.googleblog.com/2019/08/final-beta-update-official-android-q.html
This means that the operating system will not allow you to override the back gesture fully.
This makes sense as it is a fairly fundamental part of the operating system and they probably don't want to allow apps that remove the gesture entirely, as it is bad for consistency across the platform
Try this.
Define this code in your Utils class.
static List<Rect> exclusionRects = new ArrayList<>();
public static void updateGestureExclusion(AppCompatActivity activity) {
if (Build.VERSION.SDK_INT < 29) return;
exclusionRects.clear();
Rect rect = new Rect(0, 0, SystemUtil.dpToPx(activity, 16), getScreenHeight(activity));
exclusionRects.add(rect);
activity.findViewById(android.R.id.content).setSystemGestureExclusionRects(exclusionRects);
}
public static int getScreenHeight(AppCompatActivity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
return height;
}
public static int dpToPx(Context context, int i) {
return (int) (((float) i) * context.getResources().getDisplayMetrics().density);
}
Check if your layout is set in that activity where you want to exclude the edge getures and then apply this code.
// 'content' is the root view of your layout xml.
ViewTreeObserver treeObserver = content.getViewTreeObserver();
treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
content.getViewTreeObserver().removeOnGlobalLayoutListener(this);
SystemUtil.updateGestureExclusion(MainHomeActivity.this);
}
});
We are adding the view width to 16dp to trigger the code when user swipe right from left edge & height to screen height to do it fully left side.
want to turn a card in my app. I have 2 ImageViews 1 Cardfront and 2 Cardback.
My theory ->
I change the width of the cardfront from 100% to 0%
I change the width of the cardback from 0% to 100%
I googled for solution on changing the width of am ImageView while runtime, but the solution I found don't work.
// Code for step 1
ViewGroup.LayoutParams params = ivBomb.getLayoutParams();
int width = params.width;
for (int i = 1; i<width; i++) {
params.width--;
ivBomb.setLayoutParams(params);
Thread.sleep(10); // To see the change
}
When I start it without the Thread.sleep(10);, it disappears instantly. But when I start it with the Thread.sleep(10);, it waits ~7s and then disappears instantly.
What am I doing wrong?
You can use animation to get the flip card effect. This card flip animation tutorial here is for a fragment, but you can use the same animations for your views like
Animation cardFlipRightOut = AnimationUtils.loadAnimation(this, R.anim.card_flip_right_out);
cardFlipRightOut.setAnimationListener(new Animation.AnimationListener(){
#Override
public void onAnimationStart(Animation arg0) {
}
#Override
public void onAnimationRepeat(Animation arg0) {
}
#Override
public void onAnimationEnd(Animation arg0) {
Animation cardFlipLeftIn = AnimationUtils.loadAnimation(this, R.anim.card_flip_left_in);
cardFrontView.startAnimation(cardFlipLeftIn);
}
});
cardBackView.startAnimation(cardFlipRightIn);
Try calling one of these after setLayoutParams() :
1. requestLayout()
2. invalidate()
Since, requestLayout() should be called, when view's position or bounds in the parent layout have been changed, while invalidate() should be called when view's appearance has been changed. When you call requestLayout() then onLayout() and onMeasure() methods of the view will be fired, on the other hand when you call invalidate(), then onDraw() method will be fired.
I want to make a drop down menu, like a status menu, that is hidden when the activity starts, and when it's pressed or slid it opens like the image below..
http://i.stack.imgur.com/jeq5z.png
My layout currently has a RelativeLayout for the top bar and a ScrollView for the text.. between those, i'd like to put the menu..
I'm not doing this app on phonegap or anything like that, just java and xml..
Thanks in advance
Edit:
Thank you all for your help! I end up doing a FrameLayout that was set off the screen with the translationY and then, when clicked, just slide up and down.. Here's the snipped.. I'll just leave it here in case someone else needs it.
on layout.xml
<FrameLayout
android:id="#+id/layout_FrameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00ffffff" >
<!-- stuf -->
</FrameLayout>
on activity.java
private FrameLayout statusDrawer = null;
private int statusDrawerHeight; // height of the FrameLayout (generated automatically)
private int statusDrawerDragButtonHeight = 30 + 5; //height of the DragButton + height of the border
private boolean statusDrawerOpened = false;
private int statusDrawerDuration = 750; //time in milliseconds
private TimeInterpolator interpolator = null; //type of animation see#developer.android.com/reference/android/animation/TimeInterpolator.html
#Override
protected void onCreated(Bundle savedInstanceState){
statusDrawer = (FrameLayout) findViewById(R.id.layout_FrameLayout);
interpolator = new AccelerateDecelerateInterpolator();
statusDrawer.post(new Runnable() {
#Override
public void run() {
statusDrawerHeight = statusDrawer.getHeight();
statusDrawer.setTranslationY(-statusDrawerHeight+statusDrawerDragButtonHeight);
}
});
statusDrawer.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v){
if(statusDrawerOpened) {
statusDrawer.animate()
.translationY(-statusDrawerHeight+statusDrawerDragButtonHeight)
.setDuration(statusDrawerDuration)
.setInterpolator(interpolator)
.start();
} else {
statusDrawer.animate()
.translationY(0)
.setDuration(statusDrawerDuration)
.setInterpolator(interpolator)
.start();
}
statusDrawerOpened = !statusDrawerOpened;
}
});
}
Use a FrameLayout as the root layout. Add the drop menu layout as in the right side of your picture. Call
menuView.setTranslationY(-view.getHeight);
on this view to initially hide the drop down menu when the activity is started. Make sure menuView only refers to the drop down view part without the small tab button. When the user touches the tab animate translationY to 0 so that the layout will slide down
ObjectAnimator.ofFloat(dropDownView, "translationY", -view.getHeight, 0).setDuration(200).start();
whereby dropDownView refers to the complete drop down menu.
Using ObjectAnimator requires API level 11. If you need to support older API levels, use http://developer.android.com/reference/android/view/animation/TranslateAnimation.html (which has some down sides).
If you instead want add a sliding effect, e.g. the sliding menu is moving with together with the finger, install a OnTouchListener:
dropDownTab.setOnTouchListener(new OnTouchListener() {
public void onTouch(View v, MotionEvent e) {
// Make the drop down menu finger follow the finger position.
// Use again dropDownView.setTranslationY(...) to move the view.
// If the drop down menu has been dragged a certain distance, make it move out by itself using the animation as above.
}
});
So I'm using the Sliding Up Panel Library in my application, and I'm trying to implement a ScrollView inside the sliding panel. Since both the sliding panel and the ScrollView are controlled by vertical scrolls, this is causing me some issues.
I've partially got it to work by switching the panel's dragview once the panel has been slid all the way up, and when the ScrollView has been scrolled to the top.
The problem I'm facing now is that, when scrolling the panel to top the scrolling doesn't transfer to the ScrollView, like it does in Google Maps. Little hard to explain, so look at the video here:
www.youtube.com/watch?v=9MUsmQzusX8&feature=youtu.be
This is the panel slide listener:
...
slidePanel.setEnableDragViewTouchEvents(true);
slidePanel.setPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
#Override
public void onPanelSlide(View panel, float slideOffset) {
// Change the dragview to panelheader when panel is fully expanded
// I'm doing this here instead of in onPanelExpanded,
// because onPanelExpanded first gets called once scroll
// is released.
if (slideOffset <= 0) {
slidePanel.setDragView(layoutPanelTop);
}
// If the panel is not fully expanded set the whole
// panel as dragview
else if(slideOffset > 0) {
slidePanel.setDragView(layoutPanel);
}
}
}
#Override
public void onPanelExpanded(View panel) {
// layout.setDragView(layoutPanelTop);
panelCollapsed = false;
panelExpanded = true;
panelAnchored = false;
Log.v("TAG, "panelExpanded");
}
#Override
public void onPanelCollapsed(View panel) {
slidePanel.setDragView(layoutPanel);
panelCollapsed = true;
panelExpanded = false;
panelAnchored = false;
Log.v(TAG, "panelCollapsed");
}
#Override
public void onPanelAnchored(View panel) {
slidePanel.setDragView(layoutPanel);
panelCollapsed = false;
panelExpanded = false;
panelAnchored = true;
Log.v(TAG, "panelAnchored");
}
});
And I have managed to create a fully working scrollview listener by extending scrollview, which can detect scroll direction and onDown and onUp motion events:
private boolean atScrollViewTop = false;
#Override
public void onScrollChanged(int scrollY) {
scrollY = Math.min(mMaxScrollY, scrollY);
if (scrollY <= 0) {
Log.v("myTag", "You at scrollview top");
atScrollViewTop = true;
} else {
atScrollViewTop = false;
}
mScrollSettleHandler.onScroll(scrollY);
switch (mState) {
case STATE_SCROLL_UP:
if (panelExpanded && atScrollViewTop) {
slidePanel.setDragView(layoutPanel);
} else {
slidePanel.setDragView(layoutPanelTop);
}
Log.v("myTag", "scrolling up");
break;
case STATE_SCROLL_DOWN:
slidePanel.setDragView(layoutPanelTop);
Log.v("myTag", "scrolling down");
break;
}
}
#Override
public void onDownMotionEvent() {
}
#Override
public void onUpOrCancelMotionEvent() {
}
I've been struggling with this the last two days.. So really hope on some pointer at least. Thanks very much in advance. Regards Jakob Harteg.
Sorry for delay.. i find the solution.
image = (ImageView) findViewById(R.id.image); //Layout to slide
SlidingUpPanelLayout layout = (SlidingUpPanelLayout)
findViewById(R.id.sliding_layout);
layout.setDragView(image);
/*This method sets the layout to be used only
in the sliding panel, while all layouts children
are able to perform other functions such as scrolling */
And this is the layout
<..SlidingUpPanelLayout
android:id="#+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center" />
<LinearLayout
android:id="#+id/slide_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="35dp"
android:background="#drawable/ec_image"/>
<!-- FINALLY SCROLLVIEW -->
<ScrollView .... />
Hope it is useful.
I'm guessing ScrollView is child of the SlidingPanel?
In that case, override onInterceptTouchEvent to your SlidingPanel to intercept the onTouch event of your ScrollView when y = 0.
onInterceptTouchEvent does the following two:
child gets action cancel event
parent get the event trough onTouch
I don't know if I've arrived to late but after working hard some days I've found that AndroidSlidingUp panel has a method called setScrollView who handles scroll events properly.
I hope that this post will be useful because I was spending much time searching and I didn't find some tip that help me.
i have a method i created that is called throughout my project, i made it because i have a Crouton (toast) that tells a user to activate there account, or it will also alert them when there is no valid internet connection... And i dont want it to interfere with the top of my View because theres user actions that are important there. Im using a RelativeLayout
First off, this piece of code doesnt work anyways, but as i was fixing it i realized my bar i have at the bottom to switch between different Activities is now gone because it was slid down, im thinking i can just resize the hieght.
can anyone point me int he right direction for two things, one resizing height instead of sliding the whole view down, and two, help me fix the crash im getting, which occurs on setLayoutParam
i call this like this teknoloGenie.slideViewDown("100", findViewById(R.id.RootView));
public void slideViewDown(final String distX, final View view) {
final TranslateAnimation slideDown = new TranslateAnimation(0, 0, 0, Float.parseFloat(distX));
slideDown.setDuration(500);
slideDown.setFillEnabled(true);
slideDown.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)view.getLayoutParams();
params.addRule(RelativeLayout.CENTER_HORIZONTAL, -1);
params.setMargins(0, view.getTop()+Integer.parseInt(distX), 0, 0);
view.setLayoutParams(params);
view.requestLayout();
}
});
view.startAnimation(slideDown);
}
If you want to animate the height of a View, you need to write your own custom-animation and modify the LayoutParams of your animated view.
In this example, the animation animates the height of the animated View.
This is how it could look like:
public class ResizeAnimation extends Animation {
private int startHeight;
private int deltaHeight; // distance between start and end height
private View view;
/**
* constructor, do not forget to use the setParams(int, int) method before
* starting the animation
* #param v
*/
public ResizeAnimation (View v) {
this.view = v;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (startHeight + deltaHeight * interpolatedTime);
view.requestLayout();
}
/**
* set the starting and ending height for the resize animation
* starting height is usually the views current height, the end height is the height
* we want to reach after the animation is completed
* #param start height in pixels
* #param end height in pixels
*/
public void setParams(int start, int end) {
this.startHeight = start;
deltaHeight = end - startHeight;
}
#Override
public boolean willChangeBounds() {
return true;
}
}
In code, create a new Animation and apply it to the RelativeLayout that you want to animate:
View v = findViewById(R.id.youranimatedview);
// getting the layoutparams might differ in your application, it depends on the parent layout
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams();
ResizeAnimation a = new ResizeAnimation(v);
a.setDuration(500);
// set the starting height (the current height) and the new height that the view should have after the animation
a.setParams(lp.height, newHeight);
v.startAnimation(a);
To your LayoutParams problem:
My guess is that you are getting a ClassCastException because you are not using the correct LayoutParams class. If your animated view for example is contained by a RelativeLayout, you can only set RelativeLayout.LayoutParams to it. If your View is contained by a LinearLayout, you can only set LinearLayout.LayoutParams for your View.