Android 7.0 Button ripple effect not updating with button layout changes - android

I am making a productivity app with events that are represented as buttons holding basic information about the event.
The button is contained in a RelativeLayout alongside TextViews, an options menu and a switch. The parent layout's height is set to WRAP_CONTENT. It looks like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="#+id/event_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="#+id/relativeLayout"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="2dp"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:minHeight="130dp"
android:elevation="0dp"
android:textSize="#dimen/event_item_title_fontSize"
android:visibility="visible"
/>
<include
android:id="#+id/relativeLayout"
layout="#layout/_event_layout_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</include> </RelativeLayout>
You can click the button to expand it and show additional information. The expand animation is achieved using a custom animation which updates the button's getLayoutParams().height and calls getParent().requestLayout() every time applyTransformation(...) is called. This works well on an API level 19 (KitKat) device.
Animation code:
//The current state of the button which gets updated on animation finish
boolean expanded = false;
#Override
public void onClick(final View v) {
//Getting references to views
final RelativeLayout layout = (RelativeLayout) button.getParent();
final View separator = layout.findViewById(R.id.separator), list =
layout.findViewById(R.id.notes_list);
final Button button = (Button) v;
final RelativeLayout content = (RelativeLayout) layout.findViewById(R.id.relativeLayout);
final RelativeLayout.LayoutParams lpl = (RelativeLayout.LayoutParams) list.getLayoutParams(),
lpsw = (RelativeLayout.LayoutParams) layout.findViewById(R.id.event_switch).getLayoutParams(),
lp = (RelativeLayout.LayoutParams) button.getLayoutParams();
//Finished getting references to views
final int startHeight = lp.height = button.getHeight();
//Prevents button from automatically resizing to fit its parent when requestLayout is called
lp.removeRule(RelativeLayout.ALIGN_BOTTOM);
layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
layout.removeOnLayoutChangeListener(this);
final int goalHeight = GetExpectedHeight(expanded);
Animation animation = new Animation() {
float alpha1 = (expanded ? startHeight : goalHeight) - separator.getTop(),
alpha2 = (expanded ? startHeight : goalHeight) - list.getTop();
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime < 1) {
//Updates the button's height
lp.height = (int) (startHeight + (goalHeight - startHeight) * interpolatedTime);
separator.setAlpha((lp.height - separator.getTop()) / alpha1);
list.setAlpha((lp.height - list.getTop()) / alpha2);
list.setScaleX(list.getAlpha());
list.setScaleY(list.getAlpha());
layout.requestLayout();
return;
}
//Animation finish
if (expanded) {
separator.setVisibility(View.GONE);
list.setVisibility(View.GONE);
}
lp.height = lp.MATCH_PARENT;
lp.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.relativeLayout);
//A custom method that basically requests layout for whole activity
instance.get().Invalidate();
expanded = !expanded;
this.cancel();
}
};
animation.setInterpolator(new DecelerateInterpolator());
animation.setDuration(400);
layout.startAnimation(animation);
}
});
float alpha = expanded ? 1 : 0;
separator.setAlpha(alpha);
list.setAlpha(alpha);
//Kicks off the onLayoutChange method above
if (!expanded) {
separator.setVisibility(View.VISIBLE);
list.setVisibility(View.VISIBLE);
}
else
layout.requestLayout();
}
However, on an Android 7.0 Nougat (API 24), there is an accompanying ripple effect which overlays the button with a rectangle that doesn't update its height to match the button's height during the animation. I don't know what it looks like on other Android versions because those are the only two devices I can test on. Is there a way to update the ripple effect's rectangle to scale with the button? I would prefer to keep the ripple effect if at all possible. So far I have tried calling performClick() on the button each frame of the animation but to no avail.
Here is a screenshot of the button during expand animation:
And a video of the animation:
https://streamable.com/vehf3

Related

Android: LinearLayout slide off screen animation

I have two layouts (green on top, red on bottom) in a vertical LinearLayout (parent) looking similar to this:
.
When focus goes from the green to red, I would like the green to slide up off the screen and have the red simultaneously slide up with it and fill the whole screen. And when focus moves from red back up I want the green to slide back into the screen and return to the original configuration. I have tried looking at many other questions but none have had the solution I need. I tried just changing visibility between gone and visible but I want it to be a smooth animation. I've tried using parentLayout.animate().translationY(greenLayout.getHeight()) on the outer LinearLayout and that does give the animation I want but then the red does not expand to fill the screen, like this:
.
I know this question is similar to this one but that question is really old and only had one answer which didn't work for me.
My solution has a lot of different pieces, so I'll start with the full XML and java code, and then talk about the important bits:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="#+id/green"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#0f0" />
<View
android:id="#+id/red"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f00"/>
</LinearLayout>
In the XML, the only really important part is that the red view uses a height of 0dp and weight of 1. This means it takes up all extra vertical space, which will be important when we get rid of the green view.
public class MainActivity extends AppCompatActivity {
private int originalHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View green = findViewById(R.id.green);
final View red = findViewById(R.id.red);
green.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
green.getViewTreeObserver().removeGlobalOnLayoutListener(this);
originalHeight = green.getHeight();
}
});
green.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, originalHeight, 0);
}
});
red.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, 0, originalHeight);
}
});
}
private void animateHeightOfView(final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int height = (int) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = height;
view.setLayoutParams(params);
}
});
animator.start();
}
}
In the Java, the two main parts are the ViewTreeObserver.OnGlobalLayoutListener and the animateHeightOfView() method.
The OnGlobalLayoutListener exists to capture the green view's original height. We have to use a listener to do this instead of just writing originalHeight = green.getHeight() inside onCreate() because the green view isn't actually laid out at that point, so getHeight() would return 0 if we tried that.
The animateHeightOfView() method leverages the ValueAnimator class to animate the height of whatever view you pass to it. Since there's no direct setter for a view's height, we can't use simpler methods like .animate(). We set up the ValueAnimator to produce int values on every frame, and then we use a ValueAnimator.AnimatorUpdateListener to modify the view's LayoutParams to set the height.
Feel free to play with it. I'm using click listeners to trigger the animation, and you mentioned focus, but you should be able to call animateHeightOfView() in a different way if it suits you.

Change the weight of layout with an animation

In my main layout file, I have a RelativeLayout, with a weight of 1 (basically to display a map) above a LinearLayout with a weight of 2, declared this way :
<LinearLayout
android:id="#+id/GlobalLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="#+id/UpLayout"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1" >
</RelativeLayout>
<LinearLayout
android:id="#+id/DownLayout"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="2"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
DownLayout contains a list of items, when I click on an item, I would like to change the weight of DownLayout for 4, so the upper layout (the map) takes only 1/5 of the screen instead of 1/3.
I have managed to do it by changing the LayoutParams :
LinearLayout linearLayout = (LinearLayout) mActivity.findViewById(R.id.DownLayout);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
params.weight = 4.0f;
linearLayout.setLayoutParams(params);
It works but I'm not satisfied, the change is too immediate, there is no transition while I would like it to be smooth. Is there a way to use animation for that ?
I found some examples with ObjectAnimator to change the weightSum, but it does not do want I want (if I change only this property, I have some free space below my down layout) :
float ws = mLinearLayout.getWeightSum();
ObjectAnimator anim = ObjectAnimator.ofFloat(mLinearLayout, "weightSum", ws, 5.0f);
anim.setDuration(3000);
anim.addUpdateListener(this);
anim.start();
Is there a way to use ObjectAnimator (or something else) to do that ?
Thanks !
I recently came across a similar problem and solved it using a standard Animation (I have to target API 10 so couldn't use ObjectAnimator). I used a combination of the answer here with slight alterations to take into account weight instead of height.
My custom animation class looks as follows...
private class ExpandAnimation extends Animation {
private final float mStartWeight;
private final float mDeltaWeight;
public ExpandAnimation(float startWeight, float endWeight) {
mStartWeight = startWeight;
mDeltaWeight = endWeight - startWeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContent.getLayoutParams();
lp.weight = (mStartWeight + (mDeltaWeight * interpolatedTime));
mContent.setLayoutParams(lp);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
And its called by this method...
public void toggle() {
Animation a;
if (mExpanded) {
a = new ExpandAnimation(mExpandedWeight, mCollapsedWeight);
mListener.onCollapse(mContent);
} else {
a = new ExpandAnimation(mCollapsedWeight, mExpandedWeight);
mListener.onExpand(mContent);
}
a.setDuration(mAnimationDuration);
mContent.startAnimation(a);
mExpanded = !mExpanded;
}
Hopefully this will help you out, if you need more details or have questions about something let me know.

Correctly animating an Android ImageView (possibly using a Matrix)

So, I have a Layout that contains a Button and an ImageView. When you press the button the ImageView should slide out from the button like I just pulled down a rolldown curtain (bushing other views below it down). Basically what the image below show. When you press the button again the ImageView should, unlike the gif, smoothly animates up again.
.
Using this SO question I've managed to animate the height from 0 to full size but in the wrong direction. I set the scaleType to "Matrix" and the default behaviour when setting the height is to show the part from the top down to [height].
For the animation I'll need the opposite. So if I would set the height to 50dp it would show the bottom 50dp. Then I can move the ImageView down at the same time it's being revealed, thus giving the rolldown curtain effect.
I've looked throught all the different layout and view options and found nothing that seems to do this. So I'm guessing I need to specify the transformation matrix. I looked through the android.graphics.Matrix class but it's a little but too complicated for me. I simply have no idea how to use it.
If there is another, easier, way to do this then that would be fantastic but if not then I really need help with the matrix.
I'm also including the code here:
The Rolldown View XML
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/sliding_accordion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/acc_image"
android:contentDescription="#string/accord"
android:scaleType="matrix"
android:layout_below="#+id/acc_button"
android:layout_marginTop="-10dp"
android:layout_centerHorizontal="true"/>
<Button
android:id="#+id/acc_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
The implementation in code.
(Note, the MyCustomAnimation class is a copy-paste version of the class found here)
//Called from all constructors
private void create()
{
final Context context = getContext();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.widget_accordion, this, false);
final Button theButton = (Button) layout.findViewById(R.id.topic_button);
final ImageView accordionView = (ImageView) layout.findViewById(R.id.sliding_accordion);
accordionView.setVisibility(INVISIBLE);
theButton.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
if (accordionView.getVisibility() == View.VISIBLE)
{
MyCustomAnimation a = new MyCustomAnimation(accordionView, 1000, MyCustomAnimation.COLLAPSE);
height = a.getHeight();
accordionView.startAnimation(a);
}
else
{
MyCustomAnimation a = new MyCustomAnimation(accordionView, 1000, MyCustomAnimation.EXPAND);
a.setHeight(height);
accordionView.startAnimation(a);
}
}
});
this.addView(layout);
}
This took a long time perfect. But I managed to do it after a lot of experimenting.
I animate the margins of the drawer but because of the unexpected behavior of negative margins the button that opens the drawer can not be positioned on top.
When the drawer is closed the XML looks like so:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/accordion"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.animationtest.drawer.Drawer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/topic_drawer"
android:layout_centerHorizontal="true"
android:layout_marginTop="0dp"
android:visibility="invisible"/>
<com.animationtest.drawer.Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/topic_btn"
android:layout_marginTop="58dp"/>
</RelativeLayout>
Then when the button is pressed the top_margin of the drawer is increased until it has come to whatever position is needed (in this case drawerHeight - someOffset).
I used android.view.animation.Animation to animate the widget my applyTransformation function looks something like this (Note that mLayoutParams are the drawer params):
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int valueDifference = Math.abs(startValue - endValue);
float valueChange = interpolatedTime * valueDifference;
if(currentState.equals(State.COLLAPSED)) {
// is closed and I want to open it
mLayoutParams.topMargin = Math.round(interpolatedTime * valueDifference);
}
else {
// is opened and I want to close it
mLayoutParams.topMargin = valueDifference - Math.round(interpolatedTime * valueDifference);
}
drawerView.requestLayout(); //this is my drawer
}
Finally, to hide the top of the drawer as it moves, I overrode my DrawerView's dispatchDraw method to looks like so:
#Override
protected void dispatchDraw(Canvas canvas) {
float height = getHeight();
float top = height - ((LayoutParams) getLayoutParams()).topMargin;
Path path = new Path();
RectF rectF = new RectF(0.0f, top, getWidth(), height);
path.addRoundRect(rectF, 0.0f, 0.0f, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
One final note:
Because of the Button's position one would need to set the widgets margin as a negative number for it to align correctly in a list or layout. In this case it would have to be -58dp.

Align the child views in center of the ViewPager android

I need to set the child view as center of the ViewPager and also I would like to show some part of the next and previous views to the current view sides(like current screen below 1). But currently the current view is starting at left side of the ViewPager(like expected screen below 2). How can I achieve that?
Here is my code..
MyViewPagerAdapter
public class MyViewPagerAdapter extends PagerAdapter {
private Activity mActivity;
private int mPageCount;
public MyViewPagerAdapter(Activity activity,int pageCount) {
mActivity = activity;
mPageCount = pageCount;
}
#Override
public int getCount() {
return mPageCount;
}
#Override
public boolean isViewFromObject(View view, Object obj) {
return (view ==(View)obj);
}
#Override
public Object instantiateItem(ViewGroup container,final int position) {
ViewGroup viewGroup = (ViewGroup)mActivity.getLayoutInflater().inflate(
R.layout.item_view, null);
viewGroup.setBackgroundColor(randomColor());
TextView textView = (TextView)viewGroup.findViewById(R.id.textView1);
textView.setText("Page: "+(position+1));
Button button = (Button) viewGroup.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mActivity, "Hey, Its clicked!!! at page "+(position+1), Toast.LENGTH_LONG).show();
}
});
container.addView(viewGroup);
return viewGroup;
}
Random rnd = new Random();
private int randomColor(){
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
//must be overridden else throws exception as not overridden.
Log.d("Tag", collection.getChildCount()+"");
collection.removeView((View) view);
}
#Override
public float getPageWidth(int position) {
return 0.8f;
}
}
MainActivity
public class MainActivity extends Activity {
private ViewPager viewPager;
LinearLayout linearLayout;
private int ID = 100;
private final int count = 8;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewPager);
linearLayout = (LinearLayout) findViewById(R.id.indicator_layout);
generateIndicators(count);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(this, count);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
int oldPosition = 0;
#Override
public void onPageSelected(int position) {
//this changes the old position's view state image
((TextView)linearLayout.getChildAt(oldPosition)).setText("");
oldPosition = position;
//this changes the current position's view state image
((TextView)linearLayout.getChildAt(position)).setText((position+1)+"");
}
//this method will be called repeatedly upto another item comes as front one(active one)
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
//this will be called as per scroll state
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
viewPager.setOffscreenPageLimit(4);
}
private void generateIndicators(int count) {
/// Converts 14 dip into its equivalent px
int padd = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics());
for(int i=0;i<count;i++){
TextView textView = new TextView(this);
textView.setId(ID+i);
final int currentItem = i;
textView.setBackgroundResource(R.drawable.white_cell);
textView.setPadding(padd,padd,padd,padd);
/// Converts 14 dip into its equivalent px
int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
textView.setTextSize(size);
textView.setGravity(Gravity.CENTER);
/// Converts 14 dip into its equivalent px
int px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(px, px);
linearLayout.addView(textView,params);
}
((TextView)linearLayout.getChildAt(0)).setText("1");
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</android.support.v4.view.ViewPager>
<LinearLayout
android:id="#+id/indicator_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="19dp" >
</LinearLayout>
</RelativeLayout>
item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="match_parent"
android:id="#+id/root_view"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="click me" />
</LinearLayout>
Current screen
expected screen
For one app I implemented similar the following way, with standard ViewPager:
Make pages full-screen with the actual content in an inner layout. For example, make the full-screen layout a RelativeLayout with transparent background and the actual content another RelativeLayout centered in the parent. If I remember right, the reason for this was that with just the inner layout as a page, the ViewPager would not have taken all the screen width on some devices such as Galaxy Nexus.
Use ViewPager.setPageMargin() to set up a negative page margin i.e. how much of the next/previous page you want to show. Make sure it only overlaps the transparent region of the parent full-screen layout.
Call ViewPager.setOffscreenPageLimit() to adjust the off-screen page count to at least 2 from the default 1 to ensure smooth paging by really creating the pages off-screen. Otherwise you will see next/previous pages being drawn while already partially showing on screen.
For anyone upset that the OP didn't update his question with the solution here is a link that explains, with minimal effort, how to pull this off in XML: http://blog.neteril.org/blog/2013/10/14/android-tip-viewpager-with-protruding-children/
Basically when you declare your viewpager in XML, give it the same left and right padding and set android:clipToPadding="false". (The clipToPadding is missing in his xml sample and necessary to achieve this effect)
Finally, I have added my solution for this question in GitHub. I have done some pretty tricks to get the workaround solution. You can get the project from the below link(Actually I have planned to create a blog with the explanation , but I dint have that much time to do).
Here is the link(https://github.com/noundla/Sunny_Projects/tree/master/CenterLockViewPager)
You have to copy the files from com.noundla.centerviewpagersample.comps package to your project. And you can see the usage of that Viewpager in MainActivity class.
Please let me know if anyone has problems with this.
I found solution in this post, below the code i used:
// Offset between sibling pages in dp
int pageOffset = 20;
// Visible part of sibling pages at the edges in dp
int sidePageVisibleWidth = 10;
// Horizontal padding will be
int horPadding = pageOffset + sidePageVisibleWidth;
// Apply parameters
viewPager.setClipToPadding(false);
viewPager.setPageMargin(UIUtil.dpToPx(pageOffset, getContext()));
viewPager.setPadding(UIUtil.dpToPx(horPadding, getContext()), 0, UIUtil.dpToPx(horPadding, getContext()), 0);
dpToPx code:
public static int dpToPx(int dp, Context context) {
float density = context.getResources().getDisplayMetrics().density;
return Math.round((float) dp * density);
}
This is all you need
You can use padding for viewPager and set clipToPadding false
Java
viewPager.setClipToPadding(false);
viewPager.setPadding(50, 0, 50, 0);
Kotlin
viewPager.clipToPadding = false
viewPager.setPadding(50, 0, 50, 0)
I had to center current page in view pager with different page widths, so solution with paddings was not suitable. Also user scrolling was disabled (it was tab bar view pager, scrolled by another view pager). Here is a very simple solution to do that - just override ViewPager.ScrollTo method just like this (C# code, Xamarin):
public override void ScrollTo(int x, int y)
{
x -= (int) (MeasuredWidth * (1 - Adapter.GetPageWidth(CurrentItem)) / 2);
base.ScrollTo(x, y);
}
And if you calculate page width for each fragment don't forget to cache them in array.
Extend HorizontalScrollView class as the parent for the scrolling view. In the onMeasure() method you can specify the width and height of each child. Little cumbersome way but the effect will be good and you can have a good hold on your child view.

Anyway to programmatically animate layout weight property of linear layout

I have two views in a linear layout, I programmatically change their layout_weight property. Is there a way I could animate this change in weight so when the weight is changed views slides towards a new size?
You can simply use ObjectAnimator.
ObjectAnimator anim = ObjectAnimator.ofFloat(
viewToAnimate,
"weight",
startValue,
endValue);
anim.setDuration(2500);
anim.start();
The one problem is that View class has no setWeight() method (which is required by ObjectAnimator). To address this I wrote simple wrapper which helps archive view weight animation.
public class ViewWeightAnimationWrapper {
private View view;
public ViewWeightAnimationWrapper(View view) {
if (view.getLayoutParams() instanceof LinearLayout.LayoutParams) {
this.view = view;
} else {
throw new IllegalArgumentException("The view should have LinearLayout as parent");
}
}
public void setWeight(float weight) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
params.weight = weight;
view.getParent().requestLayout();
}
public float getWeight() {
return ((LinearLayout.LayoutParams) view.getLayoutParams()).weight;
}
}
Use it in this way:
ViewWeightAnimationWrapper animationWrapper = new ViewWeightAnimationWrapper(view);
ObjectAnimator anim = ObjectAnimator.ofFloat(animationWrapper,
"weight",
animationWrapper.getWeight(),
weight);
anim.setDuration(2500);
anim.start();
I have been looking at this as well. Eventually I solved it by animating the weightsum property of the parent, which works very nice if you have two views in a LinearLayout.
see:
Animating weightSum property using ObjectAnimator
In the example below, if you animate the weightSum from 1.0 to 2.0, Screen 2 will animate nicely into view.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dual_pane"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
android:weightSum="1.0">
<!-- Screen 1 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#ff0000"
android:layout_weight="1">
</LinearLayout>
<!-- Screen 2 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#ff6600"
android:layout_weight="1">
</LinearLayout>
</LinearLayout>
Note: I am not sure that this is the best way, but I tried it and it's working fine
Simply using ValueAnimator
ValueAnimator m1 = ValueAnimator.ofFloat(0.2f, 0.5f); //fromWeight, toWeight
m1.setDuration(400);
m1.setStartDelay(100); //Optional Delay
m1.setInterpolator(new LinearInterpolator());
m1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
((LinearLayout.LayoutParams) viewToAnimate.getLayoutParams()).weight = (float) animation.getAnimatedValue();
viewToAnimate.requestLayout();
}
});
m1.start();
More About ValueAnimator
Another way is to use old Animation class, as described in https://stackoverflow.com/a/20334557/2914140. In this case you can simultaneously change weights of several Views.
private static class ExpandAnimation extends Animation {
private final View[] views;
private final float startWeight;
private final float deltaWeight;
ExpandAnimation(View[] views, float startWeight, float endWeight) {
this.views = views;
this.startWeight = startWeight;
this.deltaWeight = endWeight - startWeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float weight = startWeight + (deltaWeight * interpolatedTime);
for (View view : views) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) view.getLayoutParams();
lp.weight = weight;
view.setLayoutParams(lp);
}
views[0].getParent().requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
All of the answers above weren't working for me (they would simply "snap" and not animate), but after I added weight_sum="1" to the parent layout, it started working. Just in case someone else comes up with the same issue.

Categories

Resources