I am trying to animate a brick falling, and to create the brick I add rows to a TableLayout and then add buttons to each row. The issue I am having is that when I animate the brick falling, the last button in the row is removed, rather than the brick that was animated. Here is my code:
private void setGameBoard(){
brickWall.setClipChildren(false);
brickWall.setClipToPadding(false);
//Build game board
for(int ii = 0; ii < brickRows;ii++){
final int x = ii;
//Build table rows
row = new TableRow(this.getApplicationContext());
row.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
row.setClipChildren(false);
row.setClipToPadding(false);
// row.setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.colorAccent, null));
//Build table tiles
for(int jj=0; jj < brickColumns; jj++){
final int y = jj;
final Brick tile = new Brick(this);
tile.setBackgroundColor(ResourcesCompat.getColor(getResources(), R.color.colorPrimary, null));
//Set margins to create look of tic-tac-toe
TableRow.LayoutParams lp = new TableRow.LayoutParams(
BRICK_WIDTH, BRICK_HEIGHT);
lp.setMargins(0,0,0,0);
//lp.weight = 1;
tile.setLayoutParams(lp);
if(ii % 2 == 0){
tile.setBackground(ResourcesCompat.getDrawable(getResources(),R.drawable.brick02, null));
}else if(ii % 3 == 0){
tile.setBackground(ResourcesCompat.getDrawable(getResources(),R.drawable.brick03, null));
}else if(ii % 4 == 0){
tile.setBackground(ResourcesCompat.getDrawable(getResources(),R.drawable.brick04, null));
}else{
tile.setBackground(ResourcesCompat.getDrawable(getResources(),R.drawable.brick01, null));
}
tile.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(tile.getHits() == 0){
tile.setBackground(getResources().getDrawable(R.drawable.brick01_broken01));
tile.addHit();
} else if (tile.getHits() == 1){
tile.setBackground(getResources().getDrawable(R.drawable.brick01_broken02));
tile.addHit();
}else if(tile.getHits() == 2){
brokenBricks++;
float bottomOfScreen = getResources().getDisplayMetrics()
.heightPixels;
final ViewGroup vg = (ViewGroup) brickWall.getParent().getParent();
vg.getOverlay().add(tile);
ObjectAnimator anim = ObjectAnimator.ofFloat(tile, "translationY", brickWall.getHeight());
ObjectAnimator rotate = ObjectAnimator.ofFloat(tile, "rotation", 0, 360);
rotate.setRepeatCount(Animation.INFINITE);
rotate.setRepeatMode(ValueAnimator.REVERSE);
rotate.setDuration(350);
anim.setDuration(2000);
tile.setHapticFeedbackEnabled(true);
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator arg0) {
}
#Override
public void onAnimationRepeat(Animator arg0) {
}
#Override
public void onAnimationEnd(Animator arg0) {
vg.getOverlay().remove(tile);
}
#Override
public void onAnimationCancel(Animator arg0) {
vg.getOverlay().remove(tile);
}
});
anim.setDuration(2000);
AnimatorSet set = new AnimatorSet();
set.playTogether(anim, rotate);
set.start();
//tile.setVisibility(View.INVISIBLE);
if(isRevealComplete()){
brickWall.setVisibility(View.INVISIBLE);
}
}
}
});
row.addView(tile);
}
brickWall.addView(row);
}
}
Any help would be appreciated.
Related
Part of the code works without problems. However, in the second, the animation happens, but not in an appropriate way.
The views that should slide jumps to their respective positions (they are going to the correct positions, but not animating properly).
Does anyone have the solution to this undesired behavior?
GIF showing the problem:
Code:
public void animateCollapse(){
final int count = getChildCount();
final int tWidth = getMeasuredWidth();
int widthSum = 0;
for (int i = 0; i < count; i++) {
final RelativeLayout child = (RelativeLayout) getChildAt(i);
widthSum += child.getWidth();
child.clearAnimation();
}
if(!mTagLayout.isCollapsed()){
//Removed piece of code, that works.
}
} else{
int currentPosX = 0;
if(tWidth> widthSum) return;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
int lastpos = tWidth - child.getMeasuredWidth();
float value =currentPosX - child.getX();
TranslateAnimation anim = new TranslateAnimation(0, value, 0, 0);
anim.setDuration(1000);
anim.setAnimationListener(new TranslateAnimation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) { }
#Override
public void onAnimationRepeat(Animation animation) { }
#Override
public void onAnimationEnd(Animation animation)
{
isAnimating= false;
}
});
anim.setFillAfter(true);
child.startAnimation(anim);
currentPosX += child.getMeasuredWidth();
mTagLayout.setCollapsed(false);
}
}
#Edit
The views are not being destroyed and rebuilt during the animation
I fixed by using a reverse interpolator
if (mTagLayout.isCollapsed()) {
anim.setInterpolator(new ReverseInterpolator());
mTagLayout.setCollapsed(false);
} else {
mTagLayout.setCollapsed(true);
}
public class ReverseInterpolator implements Interpolator {
#Override
public float getInterpolation(float paramFloat) {
return Math.abs(paramFloat - 1f);
}
}
Good day.I have an scenario where this half normal object animator keeps firing over and over causing heap grow and ofcourse out of memory issue at some point.Here is how it go. I have made static method for rainbow animation like this.
public static ObjectAnimator startRainbowAnimation(Context context,
String textToShow,
final TextView textViewToAttach) {
AnimatedColorSpan span = new AnimatedColorSpan(context);
final SpannableString spannableString = new SpannableString(textToShow);
String substring = textToShow;
int start = textToShow.indexOf(substring);
int end = start + substring.length();
spannableString.setSpan(span, start, end, 0);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(
span, ANIMATED_COLOR_SPAN_FLOAT_PROPERTY, 0, 100);
objectAnimator.setEvaluator(new FloatEvaluator());
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
System.gc();
Log.d("Fafasfasfas", "onAnimationUpdate: inside true");
textViewToAttach.setText(spannableString);
}
});
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(DURATION);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
return objectAnimator;
}
private static final Property<AnimatedColorSpan, Float> ANIMATED_COLOR_SPAN_FLOAT_PROPERTY
= new Property<AnimatedColorSpan, Float>(Float.class, "ANIMATED_COLOR_SPAN_FLOAT_PROPERTY") {
#Override
public void set(AnimatedColorSpan span, Float value) {
span.setTranslateXPercentage(value);
}
#Override
public Float get(AnimatedColorSpan span) {
return span.getTranslateXPercentage();
}
};
I am calling this method like this inside an recycler view adapter
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
ChatModel chatModel = chatModelList.get(position);
System.gc();
String messageBody = chatModel.getMessage().replaceAll("userid=" + chatModel.getUserId() + ":" + Constants.TYPE_MESSAGE_ATTACHMENT, "").replaceAll("userid=" + chatModel.getOpponentId() + ":" + Constants.TYPE_MESSAGE_ATTACHMENT, "");
holder.message.setText(messageBody);
if (showAsRainbow) {
if (holder.message.getTag() == null) {
objectAnimator = RainbowAnimation.startRainbowAnimation(mContext, messageBody, holder.message);
holder.message.setTag(ANIMATED);
}
} else {
objectAnimator.removeAllUpdateListeners();
objectAnimator.removeAllListeners();
objectAnimator.end();
objectAnimator.cancel();
holder.message.setTag(null);
}
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) holder.chatViewBubble.getLayoutParams();
LinearLayout.LayoutParams deliveryStatusParams = (LinearLayout.LayoutParams) holder.deliveryStatus.getLayoutParams();
LinearLayout.LayoutParams gifChatViewLayoutParams = (LinearLayout.LayoutParams) holder.imageViewWrapper.getLayoutParams();
checkForGif(chatModel, holder);
if (mCurrentUserId.equals(chatModel.getUserId())) {
layoutParams.gravity = Gravity.RIGHT;
layoutParams.rightMargin = Dp.toDps(mContext, 16);
deliveryStatusParams.gravity = Gravity.RIGHT;
gifChatViewLayoutParams.gravity = Gravity.RIGHT;
holder.chatViewBubble.setLayoutParams(layoutParams);
holder.imageView.setLayoutParams(gifChatViewLayoutParams);
holder.chatViewBubble.setBackground(ContextCompat.getDrawable(mContext, R.drawable.outgoing_message_bg));
if (chatModel.getDeliveryStatus().equals(Constants.STATUS_DELIVERED)) {
if (position >= chatModelList.size() - 1) {
holder.deliveryStatus.setVisibility(View.VISIBLE);
} else {
holder.deliveryStatus.setVisibility(View.INVISIBLE);
}
holder.deliveryStatus.setText(mContext.getString(R.string.sentText));
} else if (chatModel.getDeliveryStatus().equals(Constants.STATUS_NOT_DELIVERED)) {
holder.deliveryStatus.setVisibility(View.VISIBLE);
if (updating) {
holder.deliveryStatus.setText(mContext.getString(R.string.sendingNowText) + percentage + " %");
} else {
holder.deliveryStatus.setText(mContext.getString(R.string.sendingNowText));
}
}
} else {
holder.chatViewBubble.setBackground(ContextCompat.getDrawable(mContext, R.drawable.incoming_message_bg));
layoutParams.gravity = Gravity.LEFT;
gifChatViewLayoutParams.gravity = Gravity.LEFT;
holder.chatViewBubble.setLayoutParams(layoutParams);
holder.imageView.setLayoutParams(gifChatViewLayoutParams);
holder.deliveryStatus.setVisibility(View.INVISIBLE);
}
}
Thi issue is that if you noticed the Log.d() keeps firing even after the cancle and end was called on the objectAnimator and yes i have checked that cancel and end is being called.So i have no clue what i have done wrong.Can anyone please help me?
I see lot's of guys been looking at the post,meaning they got this issue too so i got the solution somehow fixed...Issue is that this damned ObjectAnimator when done inside an loop even with static method,each time is being created new reference to it.So you have to do something like this.Have an array list of object animators,on each call add items to array list.Whenever you want to stop it just simply loop through array and stop all object animators.Here is the code
private ArrayList<ObjectAnimator> objectAnimators = new ArrayList<>();
public void startRainbowAnimation(Context context,
final String textToShow,
final TextView textViewToAttach) {
stopCalled = false;
AnimatedColorSpan span = new AnimatedColorSpan(context);
final SpannableString spannableString = new SpannableString(textToShow);
String substring = textToShow;
int start = textToShow.indexOf(substring);
int end = start + substring.length();
spannableString.setSpan(span, start, end, 0);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(
span, animatedColorSpanFloatProperty, 0, 100);
objectAnimator.setEvaluator(new FloatEvaluator());
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
System.gc();
if (!stopCalled) {
textViewToAttach.setText(spannableString);
}
}
});
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(DateUtils.MINUTE_IN_MILLIS * 3);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
objectAnimators.add(objectAnimator);
}
objectAnimators here is an array list of object animators like this
And here is how you stop all of them to get ride of infinite update listener call firing.
public void stopRainbowAnimation() {
System.gc();
stopCalled = true;
if (!objectAnimators.isEmpty()) {
for (int i = 0; i < objectAnimators.size(); i++) {
ObjectAnimator eachAnimator = objectAnimators.get(i);
eachAnimator.setRepeatCount(0);
eachAnimator.end();
eachAnimator.cancel();
eachAnimator.removeAllListeners();
eachAnimator.removeAllUpdateListeners();
}
objectAnimators.clear();
}
}
Hope this will help someone
I have popupWindow with some image views, which are created and added progammatically to popup window. Their position is set to bottom line with setY(). But when I use setEndValue to animate with spring, image goes from 0 to setEndValue, not from it's initial position.
How that can be fixed?
public SharePostPopupWindow(View parentView) {
super(MATCH_PARENT, MATCH_PARENT);
this.context = parentView.getContext();
this.parentView = parentView;
AndroidBlaBlaApplication.component(context).inject(this);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
FrameLayout container = new FrameLayout(context);
socialViews = new ArrayList<>();
socials = new ArrayList<>();
shadow = new View(context);
shadow.setId(R.id.share_view_shadow);
shadow.setBackgroundColor(Color.BLACK);
shadow.setClickable(true);
shadow.setAlpha(0.5f);
shadow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
bus.post(new ShadowClickedEvent());
spring.setEndValue(initialPosition / 2 - Utils.convertDpToPixel(imageSize, context));
dismiss();
}
});
}
public void show() {
Utils.hideKeyboard(context, getContentView());
createButtons();
SpringSystem springSystem = SpringSystem.create();
spring = springSystem.createSpring();
SpringConfig slowConfig = new SpringConfig(TENSION, DAMPER);
spring.setSpringConfig(slowConfig);
spring.addListener(new SimpleSpringListener() {
#Override
public void onSpringUpdate(Spring spring) {
float value = (float) spring.getCurrentValue();
for (int i = 0; i < socialViews.size(); i++) {
if (i % 2 != 0) {
socialViews.get(i).setY(value);
}
}
}
});
showAtLocation(parentView, Gravity.CENTER_VERTICAL, 0, 0);
getContentView().setAlpha(1f);
}
If you want to force spring animation from certain position, you need to use
spring.setCurrentValue method first.
I'm currently using Nine Old Androids to animate a RelativeLayout like a slider menu, which slides down form the top. I've tried in many ways, Can someone make a suggestion?
Here is the animation code:
private OnClickListener slideClick = new OnClickListener(){
#Override
public void onClick(View v) {
slideGroups.bringToFront();
if(!mPulledDown) {
ObjectAnimator oa = ObjectAnimator.ofFloat(slideGroups, "translationY", slideGroups.getHeight()-80);
oa.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator arg0) {}
#Override
public void onAnimationRepeat(Animator arg0) {}
#Override
public void onAnimationEnd(Animator arg0) {
arrow.setImageResource(R.drawable.arrowup);
if(Application.getAndroidAPILevel() < 11) {
// only if pre 3.0 (api level 11)
Toast.makeText(FriendsActivity.this, String.format("api level %s",
Application.getAndroidAPILevel()), Toast.LENGTH_SHORT).show();
//RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) slideGroups.getLayoutParams();
// clear animation to prevent flicker
slideGroups.clearAnimation();
// set new "real" position of wrapper
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, slideGroups.getWidth());
//lp.addRule(RelativeLayout.BELOW, R.id.branchFinderIncludeHeader);
slideGroups.setLayoutParams(lp);
}
}
#Override
public void onAnimationCancel(Animator arg0) {}
});
oa.start();
}
else
{
ObjectAnimator oa = ObjectAnimator.ofFloat(slideGroups, "translationY", 0);
oa.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator arg0) {}
#Override
public void onAnimationRepeat(Animator arg0) {}
#Override
public void onAnimationEnd(Animator arg0) {
arrow.setImageResource(R.drawable.arrowdown);
if(Application.getAndroidAPILevel() < 11) {
// only if pre 3.0 (api level 11)
Toast.makeText(FriendsActivity.this, String.format("api level %s",
Application.getAndroidAPILevel()), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onAnimationCancel(Animator arg0) {}
});
oa.start();
}
mPulledDown = !mPulledDown;
}
};
I just need something to work with sdk level 4 and newer. Currently, touching/tapping passes that event to the list underneath the slider when expanded. Please let me know if any other information is needed. slideGroups is the RelativeLayout. arrow is an imageview. groupsButton is a LinearLayout with text and an image button. groupsButton should be clickable; that's what expands and is supposed to collapse it.
Thanks in advance.
A few people have contributed by editing my question with solutions. I'd like to give them credit, but can't upvote an edit. For posterity, here is the solution that works for me.
int originalMarginLeft = 0;
int originalMarginTop = 0;
int originalMarginRight = 0;
int originalMarginBottom = 0;
int originalLeft = 0;
int originalTop = 0;
int originalRight = 0;
int originalBottom = 0;
int originalX = 0;
int originalY = 0;
int originalHeight = 0;
private OnClickListener slideClick = new OnClickListener(){
#Override
public void onClick(View v) {
slideGroups.bringToFront();
if(originalMarginLeft == 0) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) slideGroups.getLayoutParams();
originalMarginLeft = params.leftMargin;
originalMarginTop = params.topMargin;
originalMarginRight = params.rightMargin;
originalMarginBottom = params.bottomMargin;
originalLeft = slideGroups.getLeft();
originalTop = slideGroups.getTop();
originalRight = slideGroups.getRight();
originalBottom = slideGroups.getBottom();
originalHeight = params.height;
}
if(!mPulledDown) {
ObjectAnimator oa = ObjectAnimator.ofFloat(slideGroups, "translationY", originalHeight);
oa.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator arg0) {}
#Override
public void onAnimationRepeat(Animator arg0) {}
#Override
public void onAnimationEnd(Animator arg0) {
arrow.setImageResource(R.drawable.arrowup);
if(Application.getAndroidAPILevel() < 11) {
slideGroups.clearAnimation();
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(slideGroups.getWidth(), slideGroups.getHeight());
lp.leftMargin = originalMarginLeft;
lp.rightMargin = originalMarginRight;
slideGroups.setLayoutParams(lp);
}
}
#Override
public void onAnimationCancel(Animator arg0) {}
});
oa.start();
}
else
{
ObjectAnimator oa = ObjectAnimator.ofFloat(slideGroups, "translationY", -originalHeight+60);
oa.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator arg0) {}
#Override
public void onAnimationRepeat(Animator arg0) {}
#Override
public void onAnimationEnd(Animator arg0) {
arrow.setImageResource(R.drawable.arrowdown);
if(Application.getAndroidAPILevel() < 11) {
slideGroups.clearAnimation();
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(slideGroups.getWidth(), slideGroups.getHeight());
lp.leftMargin = originalMarginLeft;
lp.rightMargin = originalMarginRight;
lp.topMargin = originalMarginTop;
lp.bottomMargin = originalMarginBottom;
slideGroups.setLayoutParams(lp);
}
}
#Override
public void onAnimationCancel(Animator arg0) {}
});
oa.start();
}
mPulledDown = !mPulledDown;
}
};
I use TranslateAnimation to move buttons and it works fine, but before animation started strange flickering apear. For short time animated view moves to position higher then it is. After that view back to normal position and animation started.
I use RelativeLayout and move 7 buttons.
public void moveView(final int rotationType) {
AnimationSet animationList[] = new AnimationSet[buttonAmount];
for (int i = 0; i < buttonAmount; i++) {
AnimationSet animationsSet = new AnimationSet(false);
animationsSet.setFillAfter(true);
boolean visible = true;
boolean hide = false;
boolean show = false;
ImageButton button = buttons.get(i);
int position = i + movementCount;
if (position < 0) {
position = getLastButtonId();
} else if (position > getLastButtonId()) {
position = position - buttonAmount;
}
int cL = getDP(leftCoord.get(position));
int cB = getDP(bottomCoord.get(position));
// TODO left right
if (rotationType == RIGHTBUTTON) {
position--;
} else if (rotationType == LEFTBUTTON) {
position++;
}
if (position < 0) {
position = getLastButtonId();
} else if (position > getLastButtonId()) {
position = position - buttonAmount;
}
int nL = getDP(leftCoord.get(position));
int nB = getDP(bottomCoord.get(position));
int left = cL - nL;
int bot = cB - nB;
long time = 1000;
if (rotationType == LEFTBUTTON) {
if (cL == getDP(leftArrowLeft) && cB == getDP(leftArrowBottom)) {
hide = false;
visible = true;
show = true;
}
if (nL == getDP(leftArrowLeft) && nB == getDP(leftArrowBottom)) {
hide = false;
visible = false;
time = 0;
}
if (nL == getDP(rightArrowLeft)
&& nB == getDP(rightArrowBottom)) {
hide = true;
}
} else
if (rotationType == RIGHTBUTTON) {
if (cL == getDP(rightArrowLeft)
&& cB == getDP(rightArrowBottom)) {
hide = false;
visible = true;
show = true;
}
if (nL == getDP(leftArrowLeft) && nB == getDP(leftArrowBottom)) {
hide = true;
}
if (nL == getDP(rightArrowLeft)
&& nB == getDP(rightArrowBottom)) {
hide = false;
visible = false;
show = false;
time = 0;
}
}
TranslateAnimation trans = new TranslateAnimation(left, 0, bot, 0);
final int l = nL;
final int b = nB;
final ImageButton ib = button;
final boolean isVisible = visible;
final boolean isHide = hide;
final boolean isShow = show;
final int count = i;
final RelativeLayout layout = main;
button.clearAnimation();
trans.setAnimationListener(new AnimationListener() {
RelativeLayout.LayoutParams params;
#Override
public void onAnimationStart(Animation animation) {
params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
params.leftMargin = l;
params.topMargin = b;
ib.setLayoutParams(params);
if (isVisible) {
int tag = (Integer) ib.getTag();
ib.setImageResource(resourcesList[tag]);
}
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
if (!isVisible) {
ib.setImageDrawable(null);
}
}
});
trans.setDuration(time);
animationsSet.addAnimation(trans);
if (show) {
Animation animShow = new AlphaAnimation(.0f, 1f);
animShow.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animation animation) {
// ib.setVisibility(View.VISIBLE);
// buttons.get(count).setVisibility(View.VISIBLE);
}
});
animShow.setDuration(1000);
animationsSet.addAnimation(animShow);
} else if (hide) {
Animation hideShow = new AlphaAnimation(1.0f, .0f);
hideShow.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animation animation) {
}
});
hideShow.setDuration(1000);
animationsSet.addAnimation(hideShow);
}
button.startAnimation(animationsSet);
}
if (rotationType == RIGHTBUTTON) {
movementCount--;
} else if (rotationType == LEFTBUTTON) {
movementCount++;
}
if (movementCount < 0) {
movementCount = getLastButtonId();
} else if (movementCount > getLastButtonId()) {
movementCount = 0;
}
}