I am using a custom animation to expand and contract my view, found on another answer here on SO. My problem is, ApplyTransformation never gets called therefore, nothing happens.
Is there anything else I'm supposed to be doing?
private void BrandTextClicked (object sender, EventArgs e)
{
Animation animation = null;
if (expanded) {
animation = new ExpandAnimation (listView, 0, height);
} else {
animation = new ExpandAnimation(listView, height, 0);
}
animation.Duration = 500;
animation.Interpolator = new AccelerateInterpolator(1);
listView.Animation = animation;
animation.StartNow ();
listView.Invalidate ();
expanded = !expanded;
}
...
public class ExpandAnimation : Animation {
private int mStartHeight;
private int mDeltaHeight;
private View mContent;
public ExpandAnimation(View content, int startHeight, int endHeight) : base() {
mContent = content;
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
public override void Initialize (int width, int height, int parentWidth, int parentHeight)
{
base.Initialize (width, height, parentWidth, parentHeight);
}
protected override void ApplyTransformation(float interpolatedTime, Transformation t) {
ViewGroup.LayoutParams lp = mContent.LayoutParameters;
lp.Height = (int) (mStartHeight + mDeltaHeight *
interpolatedTime);
mContent.LayoutParameters = lp;
mContent.RequestLayout();
}
public override bool WillChangeBounds() {
return true;
}
}
I ended up using Android.Animation package/namespace and ValueAnimation like so:
ValueAnimator animator = null;
if (expanded) {
handle.SetImageResource(Android.Resource.Drawable.ArrowDownFloat);
animator = ValueAnimator.OfObject (new HeightEvaluator (listView), height, 0);
} else {
handle.SetImageResource(Android.Resource.Drawable.ArrowUpFloat);
animator = ValueAnimator.OfObject (new HeightEvaluator (listView), 0, height);
}
animator.SetDuration(500);
animator.SetInterpolator(new AccelerateInterpolator (1));
animator.Start ();
expanded = !expanded;
Related
When expanding and collapsing my Recyclerview items, divider lines drawn multiple times or overdrawn with items.
Also when expanding and collapsing views, dividers thickness is getting reduced.
My problem is divider line drawn each and every time I expand and collapse item in Recyclerview.
So is it possible to prevent divider line drawing if it already drawn?
While expanding an item, divider line will move according to the view.?
Below is my RecyclerView Decoration Class used for divider line,
public class SeparatorDecoration extends RecyclerView.ItemDecoration {
private final Paint mPaint;
/**
* Create a decoration that draws a line in the given color and width between the items in the view.
* #param context a context to access the resources.
* #param color the color of the separator to draw.
* #param heightDp the height of the separator in dp.
*/
public SeparatorDecoration(#NonNull Context context, #ColorInt int color,
#FloatRange(from = 0, fromInclusive = false) float heightDp) {
mPaint = new Paint();
mPaint.setColor(color);
final float thickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
heightDp, context.getResources().getDisplayMetrics());
mPaint.setStrokeWidth(thickness);
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// we want to retrieve the position in the list
final int position = params.getViewAdapterPosition();
// and add a separator to any view but the last one
if (position <state.getItemCount()) {
outRect.set(40, 0, 40, (int) mPaint.getStrokeWidth()); // left, top, right, bottom
} else {
outRect.setEmpty(); // 0, 0, 0, 0
}
}
#Override
public void onDrawOver(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
final int offset = (int) (mPaint.getStrokeWidth() / 2);
// this will iterate over every visible view
for (int i = 0; i < parent.getChildCount(); i++) {
// get the view
final View view = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// get the position
final int position = params.getViewAdapterPosition();
// and finally draw the separator
if (position < parent.getChildCount()) {
final int ty = (int)(view.getTranslationY() + 0.5f);
final int top = view.getBottom() - params.bottomMargin + ty;
final int bottom = top + (int) mPaint.getStrokeWidth();
c.drawLine(view.getLeft(), view.getBottom() + offset, view.getRight(), view.getBottom() + offset, mPaint);
}
}
}
}
below is my RecyclerView Adapter class,
public class DisplayNotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private Context context;
private List<NotificationDetails> notificationRecords;
private DeleteNotificationListener deleteNotificationListener;
private String TAG = DisplayNotificationAdapter.class.getSimpleName();
interface DeleteNotificationListener {
void updateNotificationList(List<NotificationDetails> details);
}
public DisplayNotificationAdapter(Context context, DeleteNotificationListener listener, List < NotificationDetails > notificationRecordsList) {
this.context = context;
this.deleteNotificationListener = listener;
this.notificationRecords = notificationRecordsList;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder (#NonNull ViewGroup parent, int viewType){
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
AndroidLogger.log(5,TAG,"oncreate");
View listItem = layoutInflater.inflate(R.layout.display_notification_recycler_view_list_item, parent, false);
return new NotificationViewHolder(listItem);
}
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onBindViewHolder (#NonNull RecyclerView.ViewHolder holder,int position){
NotificationDetails notification = notificationRecords.get(position);
NotificationViewHolder viewHolder = (NotificationViewHolder) holder;
String currentDateString = DateFormat.getDateInstance().format(Long.parseLong(notification.getTimeStamp()));
String filePath=generateFilePath(notification.getFileName());
Bitmap myBitmap = BitmapFactory.decodeFile(filePath);
#SuppressLint("SimpleDateFormat")
DateFormat dateFormat = new SimpleDateFormat("hh:mm aa");
String time = dateFormat.format(Long.parseLong(notification.getTimeStamp()));
if (notification.isExpanded()) {
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Up Arrow")));
expandView(viewHolder.notificationImageview);
}
else {
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Down Arrow")));
collapseView(viewHolder.notificationImageview);
}
viewHolder.notificationImageview.setImageBitmap(myBitmap);
viewHolder.notificationTextView.setText(notification.getMessage());
viewHolder.notificationTimeTextView.setText(time);
Calendar now = Calendar.getInstance();
Calendar date = Calendar.getInstance();
date.setTimeInMillis(Long.parseLong(notification.getTimeStamp()));
viewHolder.expandCollapseImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(notification.isExpanded()) {
notification.setExpanded(false);
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Down Arrow")));
viewHolder.notificationTextView.setMaxLines(1);
notifyItemChanged(position);
}
else {
notification.setExpanded(true);
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Up Arrow")));
viewHolder.notificationTextView.setMaxLines(Integer.MAX_VALUE);
notifyItemChanged(position);
}
}
});
if (now.get(Calendar.DATE) == date.get(Calendar.DATE))
viewHolder.notificationDateTextView.setText("Today");
else if (now.get(Calendar.DATE) - date.get(Calendar.DATE) == 1)
viewHolder.notificationDateTextView.setText("Yesterday");
else
viewHolder.notificationDateTextView.setText(currentDateString);
if(notification.getTitle()==null)
viewHolder.notificationTitleTextView.setText("title");
else
viewHolder.notificationTitleTextView.setText(notification.getTitle());
}
private String generateFilePath(String fileName) {
File imageFileDirectory = context.getDir("image", Context.MODE_PRIVATE); //Creating an internal dir;
if (!imageFileDirectory.exists()) {
imageFileDirectory.mkdirs();
}
/*
* app server provide "U" file name after we set read status they provide same file name as "R"
*/
String createFilePath = imageFileDirectory + "/" + fileName;
return createFilePath;
}
public void removeSingleNotification ( int position){
DatabaseHelper databaseHelper = new DatabaseHelper(context);
databaseHelper.deleteSingleNotificationRecord(notificationRecords.get(position).getId());
notificationRecords.remove(position);
deleteNotificationListener.updateNotificationList(notificationRecords);
notifyDataSetChanged();
}
private void removeFromList (String id) {
for (NotificationDetails detail : notificationRecords) {
if (detail.getId().equalsIgnoreCase(id))
notificationRecords.remove(detail);
}
}
#Override
public int getItemCount () {
return notificationRecords.size();
}
public void expandView(final View v) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = v.getMeasuredHeight();
// Older versions of android (pre API 21) cancel animations for views with a height of 0.
v.getLayoutParams().height = 1;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int)(targetHeight * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// Expansion speed of 1dp/ms
a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public void collapseView(final View v) {
//collapse(pos);
final int initialHeight = v.getMeasuredHeight();
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if(interpolatedTime == 1){
v.setVisibility(View.GONE);
}else{
v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
v.requestLayout();
}
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// Collapse speed of 1dp/ms
a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public static class NotificationViewHolder extends RecyclerView.ViewHolder {
private TextView notificationTextView, notificationDateTextView, notificationTimeTextView, notificationTitleTextView;
private ImageView notificationImageview,expandCollapseImageView;
private ConstraintLayout parent;
public NotificationViewHolder(#NonNull View itemView) {
super(itemView);
notificationTextView = itemView.findViewById(R.id.notification_text_view);
notificationDateTextView = itemView.findViewById(R.id.notification_date_text_view);
notificationTimeTextView = itemView.findViewById(R.id.notification_time_text_view);
notificationTitleTextView = itemView.findViewById(R.id.notification_title_text_view);
notificationImageview = itemView.findViewById(R.id.notification_image_view);
expandCollapseImageView = itemView.findViewById(R.id.expand_collapse_arrow);
parent = itemView.findViewById(R.id.notification_parent);
}
}
}
UPDATE
I doesn't able to solve this issue. So instead of using RecyclerView.ItemDecoration I have used a View inside layout like below,
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:id="#+id/view_div"
android:background="#color/grey"
Doing like above solves the issue.
[This is my screen contains the issue][1]][1]
https://i.stack.imgur.com/CBY80.jpg
I implement a custom SpinNumberView: it is square shaped (say 40x40), it has a vertical LinearLayout as a subview, within this linear layout are a bunch of 40x40 cells stacked vertically. I want to animate the cells to scroll vertically by changing offsetY of the LinearLayout.
But there is one problem: only the cell initially in bounds (the first) is rendered, the cells outside of the bounds are not drawn, so when I animate the LinearLayout to scroll, the linear layout is spinning, but only the first cell is visible, others are blank spaces. Here is my entire code for the custom View:
public class SpinNumberView extends RelativeLayout {
private int startNumber;
private int endNumber;
private int number;
private int gridsize;
private int index;
public static final double stepDuration = 0.1;
private boolean inAnimation = true;
ArrayList<Integer> numbers;
public LinearLayout container;
public SpinNumberView(Context context) {
super(context);
}
public SpinNumberView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void dispatchDraw(Canvas canvas) {
// draw the background black solid circle
float radius = (float)(this.gridsize);
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setARGB(192, 0, 0, 0);
canvas.drawCircle(radius/2, radius/2, radius/2, p);
// draw 1px white border
Paint pp = new Paint();
pp.setStyle(Paint.Style.STROKE);
pp.setStrokeWidth(2.0f);
pp.setARGB(192, 255, 255, 255);
canvas.drawCircle(radius/2, radius/2, radius/2-1, pp);
// clip to the circle
Path path = new Path();
RectF r = new RectF((float)0.0, (float)0.0, radius, radius);
path.addRoundRect(r, radius/2, radius/2, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
#Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
super.onLayout(b, i, i1, i2, i3);
}
class AniListener implements Animator.AnimatorListener {
#Override
public void onAnimationStart(Animator animator) {}
#Override
public void onAnimationEnd(Animator animator) {
SpinNumberView.this.animateStep();
}
#Override
public void onAnimationCancel(Animator animator) {}
#Override
public void onAnimationRepeat(Animator animator) {}
}
public void animateStep() {
this.container.setTranslationY(0);
float offset;
TimeInterpolator inter;
if(this.inAnimation) {
offset = (float)this.gridsize * this.numbers.size();
inter = new LinearInterpolator();
} else {
offset = (float)this.gridsize * this.index;
inter = new DecelerateInterpolator();
}
long duration = (long)(SpinNumberView.stepDuration * this.numbers.size() * 1000);
ViewPropertyAnimator ani = this.container.animate().translationYBy(-offset).setDuration(duration);
ani.setInterpolator(inter);
if(this.inAnimation) {
ani.setListener(new AniListener());
} else {
ani.setListener(null);
}
ani.start();
}
public void stopAnimation() {
this.inAnimation = false;
}
public void startAnimation() {
this.inAnimation = true;
float offset = (float)this.gridsize * this.numbers.size();
long duration = (long)(SpinNumberView.stepDuration * this.numbers.size() * 1000);
ViewPropertyAnimator ani = this.container.animate().translationYBy(-offset).setDuration(duration);
TimeInterpolator inter = new AccelerateInterpolator();
ani.setInterpolator(inter);
ani.setListener(new AniListener());
ani.start();
}
public void setup(int number, int start, int end, int gridsize) {
this.setBackgroundColor(Color.TRANSPARENT);
this.setAlpha((float) 0.5);
this.setClipChildren(false);
this.number = number;
this.startNumber = start;
this.endNumber = end;
this.gridsize = gridsize;
this.numbers = new ArrayList<>();
for(int i=start; i<=end;i++) {
this.numbers.add(i);
}
Collections.shuffle(this.numbers);
// Find index of target number within shuffled array
this.index = this.numbers.indexOf(this.number);
this.container = new LinearLayout(this.getContext());
this.container.setOrientation(LinearLayout.VERTICAL);
this.container.setGravity(Gravity.CENTER_HORIZONTAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(this.gridsize, this.gridsize * (this.numbers.size()+1));
this.container.setLayoutParams(params);
this.addView(this.container);
int offsety = 0;
// setup all the number views
for(int k=0;k<this.numbers.size()+1;k++) {
String txt;
if(k==this.numbers.size()) {
txt = Integer.toString(this.numbers.get(0));
} else {
txt = Integer.toString(this.numbers.get(k));
}
TextView tv = new TextView(this.getContext());
tv.setLayoutParams(new LayoutParams(this.gridsize, this.gridsize));
tv.setText(txt);
tv.setTextSize(24.0f);
tv.setTextColor(Color.WHITE);
tv.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);
tv.setLines(1);
tv.setGravity(Gravity.CENTER_VERTICAL);
this.container.addView(tv);
offsety += this.gridsize;
}
this.invalidate();
}
}
Why is this happening?
BTW: I take a screenshot with getDrawingCache() of screen content, the cells are visible in the screenshot!
Yes! It happend when we get some view height or width of a view. Because didn't completely render the view when we call its height or width yet.
Solution:
Use this code to get Height and width
EditText edt = (EditText) findViewbyid(R.id.tv);
edt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int height= edt.getHeight();
int width = edt.getHeight();
edt.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
To answer my own question:
When overriding onLayout() function, I need to layout the subviews myself like this:
#Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
super.onLayout(b, i, i1, i2, i3);
this.container.layout(0, 0, this.gridsize, this.gridsize * (this.endNumber-this.startNumber+2));
}
Glad you solved it by yourself, in iOS, we use something like Redraw method for these scenarios. Hopefully it will help you to further optimize your code.
I have been searching a lot on how to animate the strike-through affect on a TextView to no results. Only thing I am getting on forums and StackOverflow is:
some_text_view.setPaintFlags(some_text_view.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG)
What I want to do is, animate the strike-through affect like in todo apps on Play Store e.g. Any.do has it on an item left-to-right swipe.
You have a couple of options:
Extend TextView and make a custom view which checks if the STRIKE_THRU_TEXT_FLAG is set and fires off an animation that will draw a small line on the text incrementing it's width on each frame of the animation.
Use an empty view and place it on your TextView (using RelativeLayout, FrameLayout etc). Make sure the dimensions of this view match exactly with your TextView. Then animate this view following the same strategy as before: Draw a horizontal line at the center of the view whose width is incremented at each frame of the animation.
If you want to know how to the animation itself, then you can look up Animator, AnimatorSet etc and their related guides.
I used this approach to make strikethrough animation:
private void animateStrikeThrough1(final TextView tv) {
final int ANIM_DURATION = 1000; //duration of animation in millis
final int length = tv.getText().length();
new CountDownTimer(ANIM_DURATION, ANIM_DURATION/length) {
Spannable span = new SpannableString(tv.getText());
StrikethroughSpan strikethroughSpan = new StrikethroughSpan();
#Override
public void onTick(long millisUntilFinished) {
//calculate end position of strikethrough in textview
int endPosition = (int) (((millisUntilFinished-ANIM_DURATION)*-1)/(ANIM_DURAT [ION/length));
endPosition = endPosition > length ?
length : endPosition;
span.setSpan(strikethroughSpan, 0, endPosition,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(span);
}
#Override
public void onFinish() {
}
}.start();
}
private fun TextView.startStrikeThroughAnimation(): ValueAnimator {
val span = SpannableString(text)
val strikeSpan = StrikethroughSpan()
val animator = ValueAnimator.ofInt(text.length)
animator.addUpdateListener {
span.setSpan(strikeSpan, 0, it.animatedValue as Int, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
text = span
invalidate()
}
animator.start()
return animator
}
private fun TextView.reverseStrikeThroughAnimation(): ValueAnimator {
val span = SpannableString(text.toString())
val strikeSpan = StrikethroughSpan()
val animator = ValueAnimator.ofInt(text.length, 0)
animator.addUpdateListener {
span.setSpan(strikeSpan, 0, it.animatedValue as Int, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
text = span
invalidate()
}
animator.start()
return animator
}
// Created by kot32 on 2017/10/26.
public class AnimationText extends TextView {
private boolean isAnimationStarted;
private float targetLength;
private float totalLength;
private Paint strikePaint;
private float startY;
//should always show Strike-Through
private boolean isDeleted;
public AnimationText(Context context, AttributeSet attrs) {
super(context, attrs);
strikePaint = new Paint();
strikePaint.setColor(Color.BLACK);
strikePaint.setAntiAlias(true);
strikePaint.setStyle(Paint.Style.FILL_AND_STROKE);
strikePaint.setStrokeWidth(5);
}
public AnimationText(Context context) {
super(context);
strikePaint = new Paint();
strikePaint.setColor(Color.BLACK);
strikePaint.setAntiAlias(true);
strikePaint.setStyle(Paint.Style.FILL_AND_STROKE);
strikePaint.setStrokeWidth(5);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isAnimationStarted) {
//画线
canvas.drawLine(0, startY, targetLength, startY, strikePaint);
}
if (isDeleted && !isAnimationStarted) {
canvas.drawLine(0, startY, totalLength, startY, strikePaint);
}
}
public void startStrikeThroughAnimation() {
totalLength = getWidth();
startY = (float) getHeight() / 2;
isAnimationStarted = true;
//利用动画逐渐画出一条删除线
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "targetLength", 0, totalLength);
objectAnimator.setInterpolator(new AccelerateInterpolator());
objectAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
isAnimationStarted = false;
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
objectAnimator.setDuration(300);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
objectAnimator.start();
postInvalidate();
}
public void setDeleted(boolean deleted) {
isDeleted = deleted;
totalLength = getWidth();
}
public float getTargetLength() {
return targetLength;
}
public void setTargetLength(float targetLength) {
this.targetLength = targetLength;
}
}
I have a view (customView) added to the WindowManager.
WindowManager mWm = (WindowManager)activity.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams(WindowManager.LayoutParams.FILL_PARENT, 0, PixelFormat.TRANSPARENT);
mWl.dimAmount = 0.0f;
mWm.addView(customView, mWl);
Inside the custom view, I will call a translate animation when close button is pressed.
//// This is the handler for the animation ////
final Handler translateHandler = new Handler();
final Runnable mtranslateUp = new Runnable() {
public void run() {
Log.v("TEST","mtranslateUp Runnable");
startAnimation(translateUp);
}
};
//// This is the listener for the close button////
View.OnClickListener closeButtonListener = new View.OnClickListener() {
public void onClick(View v) {
translateHandler.post(mtranslateUp);
}
};
//// This is the translate up animation ////
translateUp = new TranslateAnimation(0,0,0,-200);
translateUp.setFillAfter(true);
translateUp.setDuration(1000);
translateUp.setAnimationListener(new AnimationListener(){
#Override
public void onAnimationEnd(Animation animation) {
Log.v("TEST","translateUp onAnimationEnd");
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationStart(Animation animation) {
Log.v("TEST","translateUp onAnimationStart");
}}
);
If the customView is added to an activity, these code works fine!
When the customView is added to a WindowManager, the Log inside the onAnimationStart didn't show but the Log inside the Runnable can be shown.
Can anybody tells how to do animation on a view that is added to the WindowManager?
You should animate the view LayoutParameters. For example I use a method to update the view layout:
public void updateViewLayout(View view, Integer x, Integer y, Integer w, Integer h){
if (view!=null) {
WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
if(x != null)lp.x=x;
if(y != null)lp.y=y;
if(w != null && w>0)lp.width=w;
if(h != null && h>0)lp.height=h;
mWindowService.updateViewLayout(view, lp);
}
}
Obviously mWindowService is context.getSystemService(Context.WINDOW_SERVICE).
I trigger this method in the animation:
public static void overlayAnimation(final View view2animate, int viewX, int endX) {
ValueAnimator translateLeft = ValueAnimator.ofInt(viewX, endX);
translateLeft.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
updateViewLayout(view2animate, val, null, null, null);
}
});
translateLeft.setDuration(ANIMATION_DURATION);
translateLeft.start();
}
I was facing similar problem with a View attached to WindowManager.Try adding ViewGroup to WindoManager than View directly. It should work.
windowManager need a animation by android system. so the custom animation will not work
I had a problem.
When i use updateViewLayout in onAnimationUpdate, and i set the LayoutParams's width, the animation has dropped frames.
But i set the LayoutParams's x or y, the animation is ok.
like the below code:
mViewWidth = 800;
mViewHeight = 800;
final int oldX = mFloatWindowParams.x;
final int oldWidth = mFloatWindowParams.width;
final int oldHeight = mFloatWindowParams.height;
final int deltaWidth = mViewWidth - oldWidth;
final int deltaHeight = mViewHeight - oldHeight;
final boolean isWidthLarger = deltaWidth > deltaHeight;
int first = isWidthLarger ? oldWidth : oldHeight;
int end = isWidthLarger ? mViewWidth : mViewHeight;
ValueAnimator va = ValueAnimator.ofInt(first, end);
va.setDuration(1000);
va.setInterpolator(new LinearInterpolator());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
int value = (Integer) animation.getAnimatedValue();
float fraction = animation.getAnimatedFraction();
Log.i("onAnimationUpdate", value + "");
if (isWidthLarger) {
mFloatWindowParams.width = value;
mFloatWindowParams.height = oldHeight + (int) (deltaHeight * fraction);
} else {
mFloatWindowParams.width = oldWidth + (int) (deltaWidth * fraction);
mFloatWindowParams.height = value;
}
mFloatWindowParams.x = oldX - (int) (deltaWidth * fraction);
mWindowManager.updateViewLayout(mRootView, mFloatWindowParams);
}
});
va.start();
I couldn't find a good example for how to do this.
I have a RelativeLayout set with x height.
I want to add a button which expands the height to x+y height.
can someone refer me to a good example on how to do it programmatically?
You marked the solution that was closest. This is the exact solution. I had the same problem. Hopefully this answer will help others.
InstantiateResizeAnimation
ResizeAnimation resizeAnimation = new ResizeAnimation(
view,
targetHeight,
startHeight
);
resizeAnimation.setDuration(duration);
view.startAnimation(resizeAnimation);
ResizeAnimation class should look like this
public class ResizeAnimation extends Animation {
final int targetHeight;
View view;
int startHeight;
public ResizeAnimation(View view, int targetHeight, int startHeight) {
this.view = view;
this.targetHeight = targetHeight;
this.startHeight = startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) (startHeight + targetHeight * interpolatedTime);
//to support decent animation, change new heigt as Nico S. recommended in comments
//int newHeight = (int) (startHeight+(targetHeight - startHeight) * interpolatedTime);
view.getLayoutParams().height = newHeight;
view.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;
}
}
You need a scale animation here is the official documentation
this is in code
private void animate() {
ImageView imageView = (ImageView) findViewById(R.id.ImageView01);
ScaleAnimation scale = new ScaleAnimation((float)1.0, (float)1.5, (float)1.0, (float)1.5);
scale.setFillAfter(true);
scale.setDuration(500);
imageView.startAnimation(scale);
}
Please check below new edited answer as below. But here you need to know the exact new height.
public class LayoutAnimationActivity extends Activity {
RelativeLayout ril1;
Button btn;
int initialHeight;
int actualHeight;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main2);
ril1 = (RelativeLayout) findViewById(R.id.relativeLayout1);
btn = new Button(this);
btn.setWidth(100);
btn.setHeight(200);
btn.setText("Button");
actualHeight = 210;
Ani a = new Ani();
a.setDuration(2000);
ril1.startAnimation(a);
}
class Ani extends Animation {
public Ani() {}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight;
newHeight = (int) (initialHeight * interpolatedTime);
ril1.removeAllViews();
btn.setWidth(100);
btn.setHeight(300);
btn.setText("as");
ril1.addView(btn);
ril1.getLayoutParams().height = newHeight;
ril1.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
initialHeight = actualHeight;
}
#Override
public boolean willChangeBounds() {
return true;
}
};
}
Two simple ways to do this without an Animation class:
1) Set android:animateLayoutChanges="true" in you xml layout file
2) Use a ViewProperty animator
layout.setPivot(0);
layout.animate().scaleY(scaleFactor).setDuration(500);
The pivot tells the view where to scale from, default is in the middle, which in my experience is almost never what you want. The duration is optional (default = 1000).
final Button button1 = (Button) view.findViewById(R.id.button);
final CollapseAnimator animator = new CollapseAnimator(topLayout);
final ViewTreeObserver.OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int mHeight = button1.getMeasuredHeight();
KLog.i("onGlobalLayout() mHeight:" + mHeight);
animator.setValues(mHeight*2, mHeight);
}
};
button1.getViewTreeObserver().addOnGlobalLayoutListener(listener);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
view.post(new Runnable() {
#Override
public void run() {
button1.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
animator.collapse();
}
});
}
});
and class
public class CollapseAnimator {
private View view;
private boolean collapse=true;
private int duation=300;
private int destHeight=300;
private ValueAnimator animator;
private int originHeight=0;
private int from=0;
private int to=0;
public CollapseAnimator(View view ) {
this.view = view;
}
public void setValues(int destHeight,int originHeight){
this.destHeight = destHeight;
this.originHeight=originHeight;
from=originHeight;
to=originHeight;
}
public void collapse(){
from=to;
if(collapse){
to=destHeight;
collapse=false;
}else{
to=originHeight;
collapse=true;
}
KLog.i("from:" + from+",to:"+to);
animator = ValueAnimator.ofInt(from, to);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = val;
view.setLayoutParams(layoutParams);
}
});
animator.setDuration(duation);
animator.start();
}
}